// Copyright (C) 2009-2010 Amundis
// Heavily based on the OpenGL driver implemented by Nikolaus Gebhardt
// and OpenGL ES driver implemented by Christian Stehno
// This file is part of the "Irrlicht Engine".
// For conditions of distribution and use, see copyright notice in Irrlicht.h
#include "COGLES2Driver.h"
// needed here also because of the create methods' parameters
#include "CNullDriver.h"

#ifdef _IRR_COMPILE_WITH_OGLES2_

#include "COGLES2Texture.h"
#include "COGLES2MaterialRenderer.h"
#include "COGLES2NormalMapRenderer.h"
#include "COGLES2ParallaxMapRenderer.h"
#include "COGLES2Renderer2D.h"
#include "CImage.h"
#include "os.h"

#ifndef _IRR_COMPILE_WITH_ANDROID_DEVICE_
#include <EGL/egl.h>
#else
#include <android/log.h>
#endif
#include <GLES2/gl2.h>
#ifndef GL_BGRA
// we need to do this for the IMG_BGRA8888 extension
int GL_BGRA = GL_RGBA;
#endif

#include "vsopenapi.h"
extern class ClassOfSRPInterface *pSRP;

void IrrFreeSRPObject(void *Object)
{
	if( pSRP != NULL )
		pSRP -> FreeObject(Object);
}

void IrrGetSRPObjectID(void *Object,void *ObjectID)
{
	if( Object == NULL ){
		INIT_UUID((((VS_UUID *)ObjectID)[0]));
	}else{
		if( pSRP != NULL )
			pSRP -> GetID(Object,(VS_UUID *)ObjectID);
	}
}

void *IrrGetSRPObject(void *ObjectID)
{
	if( pSRP != NULL )
		return pSRP -> GetObject((VS_UUID *)ObjectID);
	return NULL;
}


namespace irr
{
    namespace video
    {

//! constructor and init code
        COGLES2Driver::COGLES2Driver( const SIrrlichtCreationParameters& params,
                                      const SExposedVideoData& data, io::IFileSystem* io
#if defined(_IRR_COMPILE_WITH_IPHONE_DEVICE_)
                                      , const MIrrIPhoneDevice& device
#endif
                                    )
                : CNullDriver( io, params.WindowSize ), COGLES2ExtensionHandler(),
                CurrentRenderMode( ERM_NONE ), ResetRenderStates( true ),
                Transformation3DChanged( true ), AntiAlias( params.AntiAlias ),
                RenderTargetTexture( 0 ), CurrentRendertargetSize( 0, 0 ), ColorFormat( ECF_R8G8B8 )
#ifndef _IRR_COMPILE_WITH_ANDROID_DEVICE_
                , EglDisplay( EGL_NO_DISPLAY )
#endif
#if defined(_IRR_COMPILE_WITH_WINDOWS_DEVICE_)
                , HDc( 0 )
#elif defined(_IRR_COMPILE_WITH_IPHONE_DEVICE_)
                , ViewFramebuffer( 0 )
                , ViewRenderbuffer( 0 )
                , ViewDepthRenderbuffer( 0 )
#endif
                , NoHighLevelShader( true )
                , BlendEnabled( false )
                , SourceFactor( EBF_ZERO )
                , DestFactor( EBF_ZERO )
        {
#ifdef _DEBUG
            setDebugName( "COGLES2Driver" );
#endif
            ExposedData = data;
#if defined(_IRR_COMPILE_WITH_WINDOWS_DEVICE_)
            EglWindow = ( NativeWindowType )data.OpenGLWin32.HWnd;
            HDc = GetDC(( HWND )EglWindow );
            EglDisplay = eglGetDisplay(( NativeDisplayType )HDc );
#elif defined(_IRR_COMPILE_WITH_X11_DEVICE_)
            EglWindow = ( NativeWindowType )ExposedData.OpenGLLinux.X11Window;
            EglDisplay = eglGetDisplay(( NativeDisplayType )ExposedData.OpenGLLinux.X11Display );
#elif defined(_IRR_COMPILE_WITH_IPHONE_DEVICE_)
            Device = device;
#endif
#ifdef EGL_VERSION_1_0
            if ( EglDisplay == EGL_NO_DISPLAY )
            {
                os::Printer::log( "Getting OpenGL-ES2 display." );
                EglDisplay = eglGetDisplay(( NativeDisplayType ) EGL_DEFAULT_DISPLAY );
            }
            if ( EglDisplay == EGL_NO_DISPLAY )
            {
                os::Printer::log( "Could not get OpenGL-ES2 display." );
            }

            EGLint majorVersion, minorVersion;
            if ( !eglInitialize( EglDisplay, &majorVersion, &minorVersion ) )
            {
                os::Printer::log( "Could not initialize OpenGL-ES2 display." );
            }
            else
            {
                char text[64];
                sprintf( text, "EglDisplay initialized. Egl version %d.%d\n", majorVersion, minorVersion );
                os::Printer::log( text );
            }

            EGLint attribs[] =
            {
                EGL_RED_SIZE, 5,
                EGL_GREEN_SIZE, 5,
                EGL_BLUE_SIZE, 5,
                EGL_ALPHA_SIZE, params.WithAlphaChannel ? 1 : 0,
                EGL_BUFFER_SIZE, 16,//params.Bits,
                EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
                //EGL_COLOR_BUFFER_TYPE, EGL_RGB_BUFFER,
                EGL_DEPTH_SIZE, params.ZBufferBits,
                EGL_STENCIL_SIZE, params.Stencilbuffer,
                EGL_SAMPLE_BUFFERS, params.AntiAlias ? 1 : 0,
                EGL_SAMPLES, params.AntiAlias,
                EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
                EGL_NONE, 0
            };
            /*EGLint attribs[] =
            {
                EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
                EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
                EGL_NONE, 0
            };*/

            EGLint contextAttrib[] =
            {
                EGL_CONTEXT_CLIENT_VERSION, 2,
                EGL_NONE, 0
            };


            EGLConfig config;
            EGLint num_configs;
            if ( !eglChooseConfig( EglDisplay, attribs, &config, 1, &num_configs ) )
            {
                os::Printer::log( "Could not get config for OpenGL-ES2 display." );
            }
            else
            {
                char log[64];
                snprintf( log, 64, "Got %d configs.\n", num_configs );
                os::Printer::log( log );
            }
            os::Printer::log( " Creating EglSurface with nativeWindow..." );
            EglSurface = eglCreateWindowSurface( EglDisplay, config, EglWindow, NULL );
            if ( EGL_NO_SURFACE == EglSurface )
            {
                os::Printer::log( "FAILED\n" );
                EglSurface = eglCreateWindowSurface( EglDisplay, config, NULL, NULL );
                os::Printer::log( "Creating EglSurface without nativeWindows..." );
            }
            else
                os::Printer::log( "SUCCESS\n" );
            if ( EGL_NO_SURFACE == EglSurface )
            {
                os::Printer::log( "FAILED\n" );
                os::Printer::log( "Could not create surface for OpenGL-ES2 display." );
            }
            else
                os::Printer::log( "SUCCESS\n" );

#ifdef EGL_VERSION_1_2
            eglBindAPI( EGL_OPENGL_ES_API );
#endif
            os::Printer::log( "Creating EglContext..." );
            EglContext = eglCreateContext( EglDisplay, config, EGL_NO_CONTEXT, contextAttrib );
            if ( testEGLError() )
            {
                os::Printer::log( "FAILED\n" );
                os::Printer::log( "Could not create Context for OpenGL-ES2 display." );
            }

            eglMakeCurrent( EglDisplay, EglSurface, EglSurface, EglContext );
            if ( testEGLError() )
            {
                os::Printer::log( "Could not make Context current for OpenGL-ES2 display." );
            }

            genericDriverInit( params.WindowSize, params.Stencilbuffer );

            // set vsync
            if ( params.Vsync )
                eglSwapInterval( EglDisplay, 1 );
#endif
#ifdef _IRR_COMPILE_WITH_ANDROID_DEVICE_
            os::Printer::log( "OpenGL-ES2 initialized" );
            __android_log_print(ANDROID_LOG_INFO, "Irrlicht", "OGLES2 initialized");
            genericDriverInit( params.WindowSize, params.Stencilbuffer );
#endif
        }


//! destructor
        COGLES2Driver::~COGLES2Driver()
        {
            deleteMaterialRenders();
            deleteAllTextures();

#ifndef _IRR_COMPILE_WITH_ANDROID_DEVICE_
            // HACK : the following is commented because destroying the context crashes under Linux ( Thibault 04-feb-10 )
            /*eglMakeCurrent( EGL_NO_DISPLAY, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT );
            eglDestroyContext( EglDisplay, EglContext );
            eglDestroySurface( EglDisplay, EglSurface );*/
            eglTerminate( EglDisplay );
#endif
#if defined(_IRR_COMPILE_WITH_WINDOWS_DEVICE_)
            if ( HDc )
                ReleaseDC(( HWND )EglWindow, HDc );
#endif

            delete TwoDRenderer;
            delete FixedPipeline;
        }

// -----------------------------------------------------------------------
// METHODS
// -----------------------------------------------------------------------

        bool COGLES2Driver::genericDriverInit( const core::dimension2d<u32>& screenSize, bool stencilBuffer )
        {
            Name = glGetString( GL_VERSION );
            printVersion();
#ifndef _IRR_COMPILE_WITH_ANDROID_DEVICE_
            os::Printer::log( eglQueryString( EglDisplay, EGL_CLIENT_APIS ) );
#endif
            // print renderer information
            vendorName = glGetString( GL_VENDOR );
            os::Printer::log( vendorName.c_str(), ELL_INFORMATION );

            u32 i;
            for ( i = 0; i < MATERIAL_MAX_TEXTURES; ++i )
                CurrentTexture[i] = 0;
            // load extensions
            initExtensions( this,
#ifndef _IRR_COMPILE_WITH_ANDROID_DEVICE_
                            EglDisplay,
#endif
                            stencilBuffer );

            StencilBuffer = stencilBuffer;

            FixedPipeline = new COGLES2FixedPipelineShader( this, FileSystem );
            FixedPipeline->useProgram(); //For setting the default uniforms (Alpha)

            TwoDRenderer = new COGLES2Renderer2d( this, FileSystem );

            glPixelStorei( GL_PACK_ALIGNMENT, 2 );

            // Reset The Current Viewport
            glViewport( 0, 0, screenSize.Width, screenSize.Height );

            setAmbientLight( SColorf( 0.0f, 0.0f, 0.0f, 0.0f ) );
#ifdef GL_separate_specular_color
            if ( FeatureAvailable[IRR_separate_specular_color] )
                glLightModeli( GL_LIGHT_MODEL_COLOR_CONTROL, GL_SEPARATE_SPECULAR_COLOR );
#endif
            glClearDepthf( 1.0f );

            //TODO : OpenGL ES 2.0 Port : GL_PERSPECTIVE_CORRECTION_HINT
            //glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_FASTEST);
            glHint( GL_GENERATE_MIPMAP_HINT, GL_FASTEST );
            glDepthFunc( GL_LEQUAL );
            glFrontFace( GL_CW );

            UserClipPlane.reallocate( 0 );

            // create material renderers
            createMaterialRenderers();

            // set the renderstates
            setRenderStates3DMode();

            // set fog mode
            setFog( FogColor, FogType, FogStart, FogEnd, FogDensity, PixelFog, RangeFog );

            // create matrix for flipping textures
            TextureFlipMatrix.buildTextureTransform( 0.0f, core::vector2df( 0, 0 ), core::vector2df( 0, 1.0f ), core::vector2df( 1.0f, -1.0f ) );

            // We need to reset once more at the beginning of the first rendering.
            // This fixes problems with intermediate changes to the material during texture load.
            ResetRenderStates = true;

            glUseProgram( 0 );
            testGLError();

            return true;
        }


        void COGLES2Driver::createMaterialRenderers()
        {
            // create OGLES1 material renderers

            addAndDropMaterialRenderer( new COGLES2MaterialRenderer_SOLID( this ) );
            addAndDropMaterialRenderer( new COGLES2MaterialRenderer_SOLID_2_LAYER( this ) );

            // add the same renderer for all lightmap types
            COGLES2MaterialRenderer_LIGHTMAP* lmr = new COGLES2MaterialRenderer_LIGHTMAP( this );
            addMaterialRenderer( lmr ); // for EMT_LIGHTMAP:
            addMaterialRenderer( lmr ); // for EMT_LIGHTMAP_ADD:
            addMaterialRenderer( lmr ); // for EMT_LIGHTMAP_M2:
            addMaterialRenderer( lmr ); // for EMT_LIGHTMAP_M4:
            addMaterialRenderer( lmr ); // for EMT_LIGHTMAP_LIGHTING:
            addMaterialRenderer( lmr ); // for EMT_LIGHTMAP_LIGHTING_M2:
            addMaterialRenderer( lmr ); // for EMT_LIGHTMAP_LIGHTING_M4:
            lmr->drop();

            // add remaining material renderer
            addAndDropMaterialRenderer( new COGLES2MaterialRenderer_DETAIL_MAP( this ) );
            addAndDropMaterialRenderer( new COGLES2MaterialRenderer_SPHERE_MAP( this ) );
            addAndDropMaterialRenderer( new COGLES2MaterialRenderer_REFLECTION_2_LAYER( this ) );
            addAndDropMaterialRenderer( new COGLES2MaterialRenderer_TRANSPARENT_ADD_COLOR( this ) );
            addAndDropMaterialRenderer( new COGLES2MaterialRenderer_TRANSPARENT_ALPHA_CHANNEL( this ) );
            addAndDropMaterialRenderer( new COGLES2MaterialRenderer_TRANSPARENT_ALPHA_CHANNEL_REF( this ) );
            addAndDropMaterialRenderer( new COGLES2MaterialRenderer_TRANSPARENT_VERTEX_ALPHA( this ) );
            addAndDropMaterialRenderer( new COGLES2MaterialRenderer_TRANSPARENT_REFLECTION_2_LAYER( this ) );

            // add normal map renderers
            s32 tmp = 0;
            video::IMaterialRenderer* renderer = 0;
            renderer = new COGLES2NormalMapRenderer( this, FileSystem, tmp, MaterialRenderers[EMT_SOLID].Renderer );
            renderer->drop();
            renderer = new COGLES2NormalMapRenderer( this, FileSystem, tmp, MaterialRenderers[EMT_TRANSPARENT_ADD_COLOR].Renderer );
            renderer->drop();
            renderer = new COGLES2NormalMapRenderer( this, FileSystem, tmp, MaterialRenderers[EMT_TRANSPARENT_VERTEX_ALPHA].Renderer );
            renderer->drop();

            // add parallax map renderers
            renderer = new COGLES2ParallaxMapRenderer( this, FileSystem, tmp, MaterialRenderers[EMT_SOLID].Renderer );
            renderer->drop();
            renderer = new COGLES2ParallaxMapRenderer( this, FileSystem, tmp, MaterialRenderers[EMT_TRANSPARENT_ADD_COLOR].Renderer );
            renderer->drop();
            renderer = new COGLES2ParallaxMapRenderer( this, FileSystem, tmp, MaterialRenderers[EMT_TRANSPARENT_VERTEX_ALPHA].Renderer );
            renderer->drop();

            // add basic 1 texture blending
            addAndDropMaterialRenderer( new COGLES2MaterialRenderer_ONETEXTURE_BLEND( this ) );
        }


//! presents the rendered scene on the screen, returns false if failed
        bool COGLES2Driver::endScene()
        {
            CNullDriver::endScene();

#ifndef _IRR_COMPILE_WITH_ANDROID_DEVICE_
            eglSwapBuffers( EglDisplay, EglSurface );
            EGLint g = eglGetError();
            if ( EGL_SUCCESS != g )
            {
                if ( EGL_CONTEXT_LOST == g )
                {
                    // o-oh, ogl-es has lost contexts...
                    os::Printer::log( "Context lost, please restart your app." );
                }
                else
                    os::Printer::log( "Could not swap buffers for OpenGL-ES2 driver." );
                return false;
            }
#endif
            return true;
        }


//! clears the zbuffer
        bool COGLES2Driver::beginScene( bool backBuffer, bool zBuffer, SColor color,
                                        const SExposedVideoData& videoData, core::rect<s32>* sourceRect )
        {
            CNullDriver::beginScene( backBuffer, zBuffer, color );

            GLbitfield mask = 0;

            if ( backBuffer )
            {
                const f32 inv = 1.0f / 255.0f;
                glClearColor( color.getRed() * inv, color.getGreen() * inv,
                              color.getBlue() * inv, color.getAlpha() * inv );

                mask |= GL_COLOR_BUFFER_BIT;
            }

            if ( zBuffer )
            {
                glDepthMask( GL_TRUE );
                LastMaterial.ZWriteEnable = true;
                mask |= GL_DEPTH_BUFFER_BIT;
            }

            glClear( mask );
            testGLError();
#ifdef _IRR_COMPILE_WITH_ANDROID_DEVICE_
//		__android_log_print(ANDROID_LOG_INFO, "Irrlicht", "begin Scene");	
#endif
			return true;
        }


//! Returns the transformation set by setTransform
        const core::matrix4& COGLES2Driver::getTransform( E_TRANSFORMATION_STATE state ) const
        {
            return Matrices[state];
        }


//! sets transformation
        void COGLES2Driver::setTransform( E_TRANSFORMATION_STATE state, const core::matrix4& mat )
        {
            Matrices[state] = mat;
            Transformation3DChanged = true;
        }

        bool COGLES2Driver::updateVertexHardwareBuffer( SHWBufferLink_opengl *HWBuffer )
        {
            if ( !HWBuffer )
                return false;

            const scene::IMeshBuffer* mb = HWBuffer->MeshBuffer;
            const void* vertices = mb->getVertices();
            const u32 vertexCount = mb->getVertexCount();
            const E_VERTEX_TYPE vType = mb->getVertexType();
            const u32 vertexSize = getVertexPitchFromType( vType );

            //buffer vertex data, and convert colours...
            core::array<c8> buffer( vertexSize * vertexCount );
            memcpy( buffer.pointer(), vertices, vertexSize * vertexCount );

            //get or create buffer
            bool newBuffer = false;
            if ( !HWBuffer->vbo_verticesID )
            {
                glGenBuffers( 1, &HWBuffer->vbo_verticesID );
                if ( !HWBuffer->vbo_verticesID ) return false;
                newBuffer = true;
            }
            else if ( HWBuffer->vbo_verticesSize < vertexCount*vertexSize )
            {
                newBuffer = true;
            }

            glBindBuffer( GL_ARRAY_BUFFER, HWBuffer->vbo_verticesID );

            //copy data to graphics card
            glGetError(); // clear error storage
            if ( !newBuffer )
                glBufferSubData( GL_ARRAY_BUFFER, 0, vertexCount * vertexSize, buffer.const_pointer() );
            else
            {
                HWBuffer->vbo_verticesSize = vertexCount * vertexSize;

                if ( HWBuffer->Mapped_Vertex == scene::EHM_STATIC )
                    glBufferData( GL_ARRAY_BUFFER, vertexCount * vertexSize, buffer.const_pointer(), GL_STATIC_DRAW );
                else
                    glBufferData( GL_ARRAY_BUFFER, vertexCount * vertexSize, buffer.const_pointer(), GL_DYNAMIC_DRAW );
            }

            glBindBuffer( GL_ARRAY_BUFFER, 0 );

            return ( glGetError() == GL_NO_ERROR );
        }


        bool COGLES2Driver::updateIndexHardwareBuffer( SHWBufferLink_opengl *HWBuffer )
        {
            if ( !HWBuffer )
                return false;

            const scene::IMeshBuffer* mb = HWBuffer->MeshBuffer;

            const void* indices = mb->getIndices();
            u32 indexCount = mb->getIndexCount();

            GLenum indexSize;
            switch ( mb->getIndexType() )
            {
                case( EIT_16BIT ):
                {
                    indexSize = sizeof( u16 );
                    break;
                }
                case( EIT_32BIT ):
                {
                    indexSize = sizeof( u32 );
                    break;
                }
                default:
                {
                    return false;
                }
            }


            //get or create buffer
            bool newBuffer = false;
            if ( !HWBuffer->vbo_indicesID )
            {
                glGenBuffers( 1, &HWBuffer->vbo_indicesID );
                if ( !HWBuffer->vbo_indicesID ) return false;
                newBuffer = true;
            }
            else if ( HWBuffer->vbo_indicesSize < indexCount*indexSize )
            {
                newBuffer = true;
            }

            glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, HWBuffer->vbo_indicesID );

            //copy data to graphics card
            glGetError(); // clear error storage
            if ( !newBuffer )
                glBufferSubData( GL_ELEMENT_ARRAY_BUFFER, 0, indexCount * indexSize, indices );
            else
            {
                HWBuffer->vbo_indicesSize = indexCount * indexSize;

                if ( HWBuffer->Mapped_Index == scene::EHM_STATIC )
                    glBufferData( GL_ELEMENT_ARRAY_BUFFER, indexCount * indexSize, indices, GL_STATIC_DRAW );
                else
                    glBufferData( GL_ELEMENT_ARRAY_BUFFER, indexCount * indexSize, indices, GL_DYNAMIC_DRAW );
            }

            glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, 0 );

            return ( glGetError() == GL_NO_ERROR );
        }


//! updates hardware buffer if needed
        bool COGLES2Driver::updateHardwareBuffer( SHWBufferLink *HWBuffer )
        {
            if ( !HWBuffer )
                return false;

            if ( HWBuffer->Mapped_Vertex != scene::EHM_NEVER )
            {
                if ( HWBuffer->ChangedID_Vertex != HWBuffer->MeshBuffer->getChangedID_Vertex()
                     || !(( SHWBufferLink_opengl* )HWBuffer )->vbo_verticesID )
                {

                    HWBuffer->ChangedID_Vertex = HWBuffer->MeshBuffer->getChangedID_Vertex();

                    if ( !updateVertexHardwareBuffer(( SHWBufferLink_opengl* )HWBuffer ) )
                        return false;
                }
            }

            if ( HWBuffer->Mapped_Index != scene::EHM_NEVER )
            {
                if ( HWBuffer->ChangedID_Index != HWBuffer->MeshBuffer->getChangedID_Index()
                     || !(( SHWBufferLink_opengl* )HWBuffer )->vbo_indicesID )
                {

                    HWBuffer->ChangedID_Index = HWBuffer->MeshBuffer->getChangedID_Index();

                    if ( !updateIndexHardwareBuffer(( SHWBufferLink_opengl* )HWBuffer ) )
                        return false;
                }
            }

            return true;
        }


//! Create hardware buffer from meshbuffer
        COGLES2Driver::SHWBufferLink *COGLES2Driver::createHardwareBuffer( const scene::IMeshBuffer* mb )
        {
            if ( !mb || ( mb->getHardwareMappingHint_Index() == scene::EHM_NEVER && mb->getHardwareMappingHint_Vertex() == scene::EHM_NEVER ) )
                return 0;

            SHWBufferLink_opengl *HWBuffer = new SHWBufferLink_opengl( mb );

            //add to map
            HWBufferMap.insert( HWBuffer->MeshBuffer, HWBuffer );

            HWBuffer->ChangedID_Vertex = HWBuffer->MeshBuffer->getChangedID_Vertex();
            HWBuffer->ChangedID_Index = HWBuffer->MeshBuffer->getChangedID_Index();
            HWBuffer->Mapped_Vertex = mb->getHardwareMappingHint_Vertex();
            HWBuffer->Mapped_Index = mb->getHardwareMappingHint_Index();
            HWBuffer->LastUsed = 0;
            HWBuffer->vbo_verticesID = 0;
            HWBuffer->vbo_indicesID = 0;
            HWBuffer->vbo_verticesSize = 0;
            HWBuffer->vbo_indicesSize = 0;

            if ( !updateHardwareBuffer( HWBuffer ) )
            {
                deleteHardwareBuffer( HWBuffer );
                return 0;
            }

            return HWBuffer;
        }


        void COGLES2Driver::deleteHardwareBuffer( SHWBufferLink *_HWBuffer )
        {
            if ( !_HWBuffer )
                return;

            SHWBufferLink_opengl *HWBuffer = ( SHWBufferLink_opengl* )_HWBuffer;
            if ( HWBuffer->vbo_verticesID )
            {
                glDeleteBuffers( 1, &HWBuffer->vbo_verticesID );
                HWBuffer->vbo_verticesID = 0;
            }
            if ( HWBuffer->vbo_indicesID )
            {
                glDeleteBuffers( 1, &HWBuffer->vbo_indicesID );
                HWBuffer->vbo_indicesID = 0;
            }

            CNullDriver::deleteHardwareBuffer( _HWBuffer );
        }


//! Draw hardware buffer
        void COGLES2Driver::drawHardwareBuffer( SHWBufferLink *_HWBuffer )
        {
            if ( !_HWBuffer )
                return;

            SHWBufferLink_opengl *HWBuffer = ( SHWBufferLink_opengl* )_HWBuffer;

            updateHardwareBuffer( HWBuffer ); //check if update is needed

            HWBuffer->LastUsed = 0;//reset count

            const scene::IMeshBuffer* mb = HWBuffer->MeshBuffer;
            const void *vertices = mb->getVertices();
            const void *indexList = mb->getIndices();

            if ( HWBuffer->Mapped_Vertex != scene::EHM_NEVER )
            {
                glBindBuffer( GL_ARRAY_BUFFER, HWBuffer->vbo_verticesID );
                vertices = 0;
            }

            if ( HWBuffer->Mapped_Index != scene::EHM_NEVER )
            {
                glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, HWBuffer->vbo_indicesID );
                indexList = 0;
            }


            drawVertexPrimitiveList( vertices, mb->getVertexCount(), indexList,
                                     mb->getIndexCount() / 3, mb->getVertexType(),
                                     scene::EPT_TRIANGLES, mb->getIndexType() );

            if ( HWBuffer->Mapped_Vertex != scene::EHM_NEVER )
                glBindBuffer( GL_ARRAY_BUFFER, 0 );

            if ( HWBuffer->Mapped_Index != scene::EHM_NEVER )
                glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, 0 );
        }


// small helper function to create vertex buffer object adress offsets
        static inline u8* buffer_offset( const long offset )
        {
            return (( u8* )0 + offset );
        }


		//! draws a vertex primitive list
        void COGLES2Driver::drawVertexPrimitiveList( const void* vertices, u32 vertexCount,
                                                     const void* indexList, u32 primitiveCount,
                                                     E_VERTEX_TYPE vType, scene::E_PRIMITIVE_TYPE pType, E_INDEX_TYPE iType )
        {
            testGLError();
            if ( !checkPrimitiveCount( primitiveCount ) )
                return;

            setRenderStates3DMode();

            drawVertexPrimitiveList2d3d( vertices, vertexCount, ( const u16* )indexList, primitiveCount, vType, pType, iType );

            if ( static_cast<u32>( Material.MaterialType ) < MaterialRenderers.size() )
                MaterialRenderers[Material.MaterialType].Renderer->PostRender( this, video::EVT_STANDARD );
        }


        void COGLES2Driver::drawVertexPrimitiveList2d3d( const void* vertices, u32 vertexCount,
                                                         const void* indexList, u32 primitiveCount,
                                                         E_VERTEX_TYPE vType, scene::E_PRIMITIVE_TYPE pType, E_INDEX_TYPE iType, bool threed )
        {
            if ( !primitiveCount || !vertexCount )
                return;

            if ( !threed && !checkPrimitiveCount( primitiveCount ) )
                return;

            CNullDriver::drawVertexPrimitiveList( vertices, vertexCount, indexList, primitiveCount, vType, pType, iType );

            //TODO: treat #ifdef GL_OES_point_size_array outside this if
            if ( NoHighLevelShader )
            {
                glEnableVertexAttribArray( EVA_COLOR );
                glEnableVertexAttribArray( EVA_POSITION );
                if (( pType != scene::EPT_POINTS ) && ( pType != scene::EPT_POINT_SPRITES ) )
                {
                    glEnableVertexAttribArray( EVA_TCOORD0 );
                }
#ifdef GL_OES_point_size_array
                else if ( FeatureAvailable[IRR_OES_point_size_array] && ( Material.Thickness == 0.0f ) )
                    glEnableClientState( GL_POINT_SIZE_ARRAY_OES );
#endif
                if ( threed && ( pType != scene::EPT_POINTS ) && ( pType != scene::EPT_POINT_SPRITES ) )
                {
                    glEnableVertexAttribArray( EVA_NORMAL );
                }

                switch ( vType )
                {
                    case EVT_STANDARD:
                        if ( vertices )
                        {
#ifdef GL_OES_point_size_array
                            if (( pType == scene::EPT_POINTS ) || ( pType == scene::EPT_POINT_SPRITES ) )
                            {
                                if ( FeatureAvailable[IRR_OES_point_size_array] && ( Material.Thickness == 0.0f ) )
                                    glPointSizePointerOES( GL_FLOAT, sizeof( S3DVertex ), &( static_cast<const S3DVertex*>( vertices ) )[0].Normal.X );
                            }
                            else
#endif
                                glVertexAttribPointer( EVA_POSITION, ( threed ? 3 : 2 ), GL_FLOAT, false, sizeof( S3DVertex ), &( static_cast<const S3DVertex*>( vertices ) )[0].Pos );
                            if ( threed )
                                glVertexAttribPointer( EVA_NORMAL, 3, GL_FLOAT, false, sizeof( S3DVertex ), &( static_cast<const S3DVertex*>( vertices ) )[0].Normal );
                            glVertexAttribPointer( EVA_COLOR, 4, GL_UNSIGNED_BYTE, true, sizeof( S3DVertex ), &( static_cast<const S3DVertex*>( vertices ) )[0].Color );
                            glVertexAttribPointer( EVA_TCOORD0, 2, GL_FLOAT, false, sizeof( S3DVertex ), &( static_cast<const S3DVertex*>( vertices ) )[0].TCoords );

                        }
                        else
                        {
                            glVertexAttribPointer( EVA_POSITION, 3, GL_FLOAT, false, sizeof( S3DVertex ), 0 );
                            glVertexAttribPointer( EVA_NORMAL, 3, GL_FLOAT, false, sizeof( S3DVertex ), buffer_offset( 12 ) );
                            glVertexAttribPointer( EVA_COLOR, 4, GL_UNSIGNED_BYTE, true, sizeof( S3DVertex ), buffer_offset( 24 ) );
                            glVertexAttribPointer( EVA_TCOORD0, 2, GL_FLOAT, false, sizeof( S3DVertex ), buffer_offset( 28 ) );
                        }

                        if ( CurrentTexture[1] )
                        {
                            // There must be some optimisation here as it uses the same texture coord !
                            glEnableVertexAttribArray( EVA_TCOORD1 );
                            if ( vertices )
                                glVertexAttribPointer( EVA_TCOORD1, 2, GL_FLOAT, false, sizeof( S3DVertex ), &( static_cast<const S3DVertex*>( vertices ) )[0].TCoords );
                            else
                                glVertexAttribPointer( EVA_TCOORD1, 2, GL_FLOAT, false, sizeof( S3DVertex ), buffer_offset( 28 ) );
                        }
                        break;
                    case EVT_2TCOORDS:
                        glEnableVertexAttribArray( EVA_TCOORD1 );
                        if ( vertices )
                        {
                            glVertexAttribPointer( EVA_POSITION, ( threed ? 3 : 2 ), GL_FLOAT, false, sizeof( S3DVertex2TCoords ), &( static_cast<const S3DVertex2TCoords*>( vertices ) )[0].Pos );
                            if ( threed )
                                glVertexAttribPointer( EVA_NORMAL, 3, GL_FLOAT, false, sizeof( S3DVertex2TCoords ), &( static_cast<const S3DVertex2TCoords*>( vertices ) )[0].Normal );
                            glVertexAttribPointer( EVA_COLOR, 4, GL_UNSIGNED_BYTE, true, sizeof( S3DVertex2TCoords ), &( static_cast<const S3DVertex2TCoords*>( vertices ) )[0].Color );
                            glVertexAttribPointer( EVA_TCOORD0, 2, GL_FLOAT, false, sizeof( S3DVertex2TCoords ), &( static_cast<const S3DVertex2TCoords*>( vertices ) )[0].TCoords );
                            glVertexAttribPointer( EVA_TCOORD1, 2, GL_FLOAT, false, sizeof( S3DVertex2TCoords ), &( static_cast<const S3DVertex2TCoords*>( vertices ) )[0].TCoords2 );
                        }
                        else
                        {
                            glVertexAttribPointer( EVA_POSITION, 3, GL_FLOAT, false, sizeof( S3DVertex2TCoords ), buffer_offset( 0 ) );
                            glVertexAttribPointer( EVA_NORMAL, 3, GL_FLOAT, false, sizeof( S3DVertex2TCoords ), buffer_offset( 12 ) );
                            glVertexAttribPointer( EVA_COLOR, 4, GL_UNSIGNED_BYTE, true, sizeof( S3DVertex2TCoords ), buffer_offset( 24 ) );
                            glVertexAttribPointer( EVA_TCOORD0, 2, GL_FLOAT, false, sizeof( S3DVertex2TCoords ), buffer_offset( 28 ) );
                            glVertexAttribPointer( EVA_TCOORD1, 2, GL_FLOAT, false, sizeof( S3DVertex2TCoords ), buffer_offset( 36 ) );

                        }
                        break;
                    case EVT_TANGENTS:
                        glEnableVertexAttribArray( EVA_TANGENT );
                        glEnableVertexAttribArray( EVA_BINORMAL );
                        if ( vertices )
                        {
                            glVertexAttribPointer( EVA_POSITION, ( threed ? 3 : 2 ), GL_FLOAT, false, sizeof( S3DVertexTangents ), &( static_cast<const S3DVertexTangents*>( vertices ) )[0].Pos );
                            if ( threed )
                                glVertexAttribPointer( EVA_NORMAL, 3, GL_FLOAT, false, sizeof( S3DVertexTangents ), &( static_cast<const S3DVertexTangents*>( vertices ) )[0].Normal );
                            glVertexAttribPointer( EVA_COLOR, 4, GL_UNSIGNED_BYTE, true, sizeof( S3DVertexTangents ), &( static_cast<const S3DVertexTangents*>( vertices ) )[0].Color );
                            glVertexAttribPointer( EVA_TCOORD0, 2, GL_FLOAT, false, sizeof( S3DVertexTangents ), &( static_cast<const S3DVertexTangents*>( vertices ) )[0].TCoords );
                            glVertexAttribPointer( EVA_TANGENT, 3, GL_FLOAT, false, sizeof( S3DVertexTangents ), &( static_cast<const S3DVertexTangents*>( vertices ) )[0].Tangent );
                            glVertexAttribPointer( EVA_BINORMAL, 3, GL_FLOAT, false, sizeof( S3DVertexTangents ), &( static_cast<const S3DVertexTangents*>( vertices ) )[0].Binormal );
                        }
                        else
                        {
                            glVertexAttribPointer( EVA_POSITION, 3, GL_FLOAT, false, sizeof( S3DVertexTangents ), buffer_offset( 0 ) );
                            glVertexAttribPointer( EVA_NORMAL, 3, GL_FLOAT, false, sizeof( S3DVertexTangents ), buffer_offset( 12 ) );
                            glVertexAttribPointer( EVA_COLOR, 4, GL_UNSIGNED_BYTE, true, sizeof( S3DVertexTangents ), buffer_offset( 24 ) );
                            glVertexAttribPointer( EVA_TCOORD0, 2, GL_FLOAT, false, sizeof( S3DVertexTangents ), buffer_offset( 28 ) );
                            glVertexAttribPointer( EVA_TANGENT, 3, GL_FLOAT, false, sizeof( S3DVertexTangents ), buffer_offset( 36 ) );
                            glVertexAttribPointer( EVA_BINORMAL, 3, GL_FLOAT, false, sizeof( S3DVertexTangents ), buffer_offset( 48 ) );
                        }
                        break;
                }
            }

            // draw everything
            GLenum indexSize = 0;

            switch ( iType )
            {
                case( EIT_16BIT ):
                {
                    indexSize = GL_UNSIGNED_SHORT;
                    break;
                }
                case( EIT_32BIT ):
                {
#ifdef GL_OES_element_index_uint
#ifndef GL_UNSIGNED_INT
#define GL_UNSIGNED_INT                   0x1405
#endif
                    if ( FeatureAvailable[IRR_OES_element_index_uint] )
                        indexSize = GL_UNSIGNED_INT;
                    else
#endif
                        indexSize = GL_UNSIGNED_SHORT;
                    break;
                }
            }

            switch ( pType )
            {
                case scene::EPT_POINTS:
                case scene::EPT_POINT_SPRITES:
                {
#ifdef GL_OES_point_sprite
                    if ( pType == scene::EPT_POINT_SPRITES && FeatureAvailable[IRR_OES_point_sprite] )
                        glEnable( GL_POINT_SPRITE_OES );
#endif
                    // if ==0 we use the point size array
                    if ( Material.Thickness != 0.f )
                    {
//                        float quadratic[] = {0.0f, 0.0f, 10.01f};
                        //TODO : OpenGL ES 2.0 Port GL_POINT_DISTANCE_ATTENUATION
                        //glPointParameterfv(GL_POINT_DISTANCE_ATTENUATION, quadratic);
//                        float maxParticleSize = 1.0f;
                        //TODO : OpenGL ES 2.0 Port GL_POINT_SIZE_MAX
                        //glGetFloatv(GL_POINT_SIZE_MAX, &maxParticleSize);
//              maxParticleSize=maxParticleSize<Material.Thickness?maxParticleSize:Material.Thickness;
//              glPointParameterf(GL_POINT_SIZE_MAX,maxParticleSize);
//              glPointParameterf(GL_POINT_SIZE_MIN,Material.Thickness);
                        //TODO : OpenGL ES 2.0 Port GL_POINT_FADE_THRESHOLD_SIZE
                        //glPointParameterf(GL_POINT_FADE_THRESHOLD_SIZE, 60.0f);
                        //glPointSize(Material.Thickness);
                    }
#ifdef GL_OES_point_sprite
                    if ( pType == scene::EPT_POINT_SPRITES && FeatureAvailable[IRR_OES_point_sprite] )
                        glTexEnvf( GL_POINT_SPRITE_OES, GL_COORD_REPLACE_OES, GL_TRUE );
#endif
                    glDrawArrays( GL_POINTS, 0, primitiveCount );
#ifdef GL_OES_point_sprite
                    if ( pType == scene::EPT_POINT_SPRITES && FeatureAvailable[IRR_OES_point_sprite] )
                    {
                        glDisable( GL_POINT_SPRITE_OES );
                        glTexEnvf( GL_POINT_SPRITE_OES, GL_COORD_REPLACE_OES, GL_FALSE );
                    }
#endif
                }
                break;
                case scene::EPT_LINE_STRIP:
                    glDrawElements( GL_LINE_STRIP, primitiveCount + 1, indexSize, indexList );
                    break;
                case scene::EPT_LINE_LOOP:
                    glDrawElements( GL_LINE_LOOP, primitiveCount, indexSize, indexList );
                    break;
                case scene::EPT_LINES:
                    glDrawElements( GL_LINES, primitiveCount*2, indexSize, indexList );
                    break;
                case scene::EPT_TRIANGLE_STRIP:
                    glDrawElements( GL_TRIANGLE_STRIP, primitiveCount + 2, indexSize, indexList );
                    break;
                case scene::EPT_TRIANGLE_FAN:
                    glDrawElements( GL_TRIANGLE_FAN, primitiveCount + 2, indexSize, indexList );
                    break;
                case scene::EPT_TRIANGLES:
                    glDrawElements(( LastMaterial.Wireframe ) ? GL_LINES : ( LastMaterial.PointCloud ) ? GL_POINTS : GL_TRIANGLES, primitiveCount*3, indexSize, indexList );
                    break;
                case scene::EPT_QUAD_STRIP:
// TODO ogl-es
//          glDrawElements(GL_QUAD_STRIP, primitiveCount*2+2, indexSize, indexList);
                    break;
                case scene::EPT_QUADS:
// TODO ogl-es
//          glDrawElements(GL_QUADS, primitiveCount*4, indexSize, indexList);
                    break;
                case scene::EPT_POLYGON:
// TODO ogl-es
//          glDrawElements(GL_POLYGON, primitiveCount, indexSize, indexList);
                    break;
            }

            if ( NoHighLevelShader )
            {
                if ( vType == EVT_TANGENTS )
                {
                    glDisableVertexAttribArray( EVA_TANGENT );
                    glDisableVertexAttribArray( EVA_BINORMAL );
                }
                if (( vType != EVT_STANDARD ) || CurrentTexture[1] )
                {
                    glDisableVertexAttribArray( EVA_TCOORD1 );
                }

#ifdef GL_OES_point_size_array
                if ( FeatureAvailable[IRR_OES_point_size_array] && ( Material.Thickness == 0.0f ) )
                    glDisableClientState( GL_POINT_SIZE_ARRAY_OES );
#endif
                glDisableVertexAttribArray( EVA_POSITION );
                glDisableVertexAttribArray( EVA_NORMAL );
                glDisableVertexAttribArray( EVA_COLOR );
                glDisableVertexAttribArray( EVA_TCOORD0 );
            }
            testGLError();
        }


//! draws a 2d image, using a color and the alpha channel of the texture
        void COGLES2Driver::draw2DImage( const video::ITexture* texture,
                                         const core::position2d<s32>& pos,
                                         const core::rect<s32>& sourceRect,
                                         const core::rect<s32>* clipRect, SColor color,
                                         bool useAlphaChannelOfTexture )
        {
            if ( !texture )
                return;

            if ( !sourceRect.isValid() )
                return;

            core::position2d<s32> targetPos( pos );
            core::position2d<s32> sourcePos( sourceRect.UpperLeftCorner );
            core::dimension2d<s32> sourceSize( sourceRect.getSize() );
            if ( clipRect )
            {
                if ( targetPos.X < clipRect->UpperLeftCorner.X )
                {
                    sourceSize.Width += targetPos.X - clipRect->UpperLeftCorner.X;
                    if ( sourceSize.Width <= 0 )
                        return;

                    sourcePos.X -= targetPos.X - clipRect->UpperLeftCorner.X;
                    targetPos.X = clipRect->UpperLeftCorner.X;
                }

                if ( targetPos.X + sourceSize.Width > clipRect->LowerRightCorner.X )
                {
                    sourceSize.Width -= ( targetPos.X + sourceSize.Width ) - clipRect->LowerRightCorner.X;
                    if ( sourceSize.Width <= 0 )
                        return;
                }

                if ( targetPos.Y < clipRect->UpperLeftCorner.Y )
                {
                    sourceSize.Height += targetPos.Y - clipRect->UpperLeftCorner.Y;
                    if ( sourceSize.Height <= 0 )
                        return;

                    sourcePos.Y -= targetPos.Y - clipRect->UpperLeftCorner.Y;
                    targetPos.Y = clipRect->UpperLeftCorner.Y;
                }

                if ( targetPos.Y + sourceSize.Height > clipRect->LowerRightCorner.Y )
                {
                    sourceSize.Height -= ( targetPos.Y + sourceSize.Height ) - clipRect->LowerRightCorner.Y;
                    if ( sourceSize.Height <= 0 )
                        return;
                }
            }

            // clip these coordinates

            if ( targetPos.X < 0 )
            {
                sourceSize.Width += targetPos.X;
                if ( sourceSize.Width <= 0 )
                    return;

                sourcePos.X -= targetPos.X;
                targetPos.X = 0;
            }

            const core::dimension2d<u32>& renderTargetSize = getCurrentRenderTargetSize();

            if ( targetPos.X + sourceSize.Width > ( s32 )renderTargetSize.Width )
            {
                sourceSize.Width -= ( targetPos.X + sourceSize.Width ) - renderTargetSize.Width;
                if ( sourceSize.Width <= 0 )
                    return;
            }

            if ( targetPos.Y < 0 )
            {
                sourceSize.Height += targetPos.Y;
                if ( sourceSize.Height <= 0 )
                    return;

                sourcePos.Y -= targetPos.Y;
                targetPos.Y = 0;
            }

            if ( targetPos.Y + sourceSize.Height > ( s32 )renderTargetSize.Height )
            {
                sourceSize.Height -= ( targetPos.Y + sourceSize.Height ) - renderTargetSize.Height;
                if ( sourceSize.Height <= 0 )
                    return;
            }

            // ok, we've clipped everything.
            // now draw it.

            // texcoords need to be flipped horizontally for RTTs
            const bool isRTT = texture->isRenderTarget();
            const core::dimension2d<u32>& ss = texture->getOriginalSize();
            const f32 invW = 1.f / static_cast<f32>( ss.Width );
            const f32 invH = 1.f / static_cast<f32>( ss.Height );
            const core::rect<f32> tcoords(
                sourcePos.X * invW,
                ( isRTT ? ( sourcePos.Y + sourceSize.Height ) : sourcePos.Y ) * invH,
                ( sourcePos.X + sourceSize.Width ) * invW,
                ( isRTT ? sourcePos.Y : ( sourcePos.Y + sourceSize.Height ) ) * invH );

            const core::rect<s32> poss( targetPos, sourceSize );

            disableTextures( 1 );
            if ( !setTexture( 0, texture ) )
                return;
            setRenderStates2DMode( color.getAlpha() < 255, true, useAlphaChannelOfTexture );

            u16 indices[] = {0, 1, 2, 3};
            S3DVertex vertices[4];
            vertices[0] = S3DVertex(( f32 )poss.UpperLeftCorner.X, ( f32 )poss.UpperLeftCorner.Y, 0, 0, 0, 1, color, tcoords.UpperLeftCorner.X, tcoords.UpperLeftCorner.Y );
            vertices[1] = S3DVertex(( f32 )poss.LowerRightCorner.X, ( f32 )poss.UpperLeftCorner.Y, 0, 0, 0, 1, color, tcoords.LowerRightCorner.X, tcoords.UpperLeftCorner.Y );
            vertices[2] = S3DVertex(( f32 )poss.LowerRightCorner.X, ( f32 )poss.LowerRightCorner.Y, 0, 0, 0, 1, color, tcoords.LowerRightCorner.X, tcoords.LowerRightCorner.Y );
            vertices[3] = S3DVertex(( f32 )poss.UpperLeftCorner.X, ( f32 )poss.LowerRightCorner.Y, 0, 0, 0, 1, color, tcoords.UpperLeftCorner.X, tcoords.LowerRightCorner.Y );
            drawVertexPrimitiveList2d3d( vertices, 4, indices, 2, video::EVT_STANDARD, scene::EPT_TRIANGLE_FAN, EIT_16BIT, false );
        }

        void COGLES2Driver::draw2DImageBatch( const video::ITexture* texture,
                                              const core::array<core::position2d<s32> >& positions,
                                              const core::array<core::rect<s32> >& sourceRects,
                                              const core::rect<s32>* clipRect,
                                              SColor color,
                                              bool useAlphaChannelOfTexture )
        {
            if ( !texture )
                return;

            if ( !setTexture( 0, const_cast<video::ITexture*>( texture ) ) )
                return;

            const irr::u32 drawCount = core::min_<u32>( positions.size(), sourceRects.size() );

            core::array<S3DVertex> vtx( drawCount * 4 );
            core::array<u16> indices( drawCount * 6 );

            for ( u32 i = 0; i < drawCount; i++ )
            {
                core::position2d<s32> targetPos = positions[i];
                core::position2d<s32> sourcePos = sourceRects[i].UpperLeftCorner;
                // This needs to be signed as it may go negative.
                core::dimension2d<s32> sourceSize( sourceRects[i].getSize() );

                if ( clipRect )
                {
                    if ( targetPos.X < clipRect->UpperLeftCorner.X )
                    {
                        sourceSize.Width += targetPos.X - clipRect->UpperLeftCorner.X;
                        if ( sourceSize.Width <= 0 )
                            continue;

                        sourcePos.X -= targetPos.X - clipRect->UpperLeftCorner.X;
                        targetPos.X = clipRect->UpperLeftCorner.X;
                    }

                    if ( targetPos.X + ( s32 )sourceSize.Width > clipRect->LowerRightCorner.X )
                    {
                        sourceSize.Width -= ( targetPos.X + sourceSize.Width ) - clipRect->LowerRightCorner.X;
                        if ( sourceSize.Width <= 0 )
                            continue;
                    }

                    if ( targetPos.Y < clipRect->UpperLeftCorner.Y )
                    {
                        sourceSize.Height += targetPos.Y - clipRect->UpperLeftCorner.Y;
                        if ( sourceSize.Height <= 0 )
                            continue;

                        sourcePos.Y -= targetPos.Y - clipRect->UpperLeftCorner.Y;
                        targetPos.Y = clipRect->UpperLeftCorner.Y;
                    }

                    if ( targetPos.Y + ( s32 )sourceSize.Height > clipRect->LowerRightCorner.Y )
                    {
                        sourceSize.Height -= ( targetPos.Y + sourceSize.Height ) - clipRect->LowerRightCorner.Y;
                        if ( sourceSize.Height <= 0 )
                            continue;
                    }
                }

                // clip these coordinates

                if ( targetPos.X < 0 )
                {
                    sourceSize.Width += targetPos.X;
                    if ( sourceSize.Width <= 0 )
                        continue;

                    sourcePos.X -= targetPos.X;
                    targetPos.X = 0;
                }

                const core::dimension2d<u32>& renderTargetSize = getCurrentRenderTargetSize();

                if ( targetPos.X + sourceSize.Width > ( s32 )renderTargetSize.Width )
                {
                    sourceSize.Width -= ( targetPos.X + sourceSize.Width ) - renderTargetSize.Width;
                    if ( sourceSize.Width <= 0 )
                        continue;
                }

                if ( targetPos.Y < 0 )
                {
                    sourceSize.Height += targetPos.Y;
                    if ( sourceSize.Height <= 0 )
                        continue;

                    sourcePos.Y -= targetPos.Y;
                    targetPos.Y = 0;
                }

                if ( targetPos.Y + sourceSize.Height > ( s32 )renderTargetSize.Height )
                {
                    sourceSize.Height -= ( targetPos.Y + sourceSize.Height ) - renderTargetSize.Height;
                    if ( sourceSize.Height <= 0 )
                        continue;
                }

                // ok, we've clipped everything.
                // now draw it.

                core::rect<f32> tcoords;
                tcoords.UpperLeftCorner.X = ((( f32 )sourcePos.X ) ) / texture->getOriginalSize().Width ;
                tcoords.UpperLeftCorner.Y = ((( f32 )sourcePos.Y ) ) / texture->getOriginalSize().Height;
                tcoords.LowerRightCorner.X = tcoords.UpperLeftCorner.X + (( f32 )( sourceSize.Width ) / texture->getOriginalSize().Width );
                tcoords.LowerRightCorner.Y = tcoords.UpperLeftCorner.Y + (( f32 )( sourceSize.Height ) / texture->getOriginalSize().Height );

                const core::rect<s32> poss( targetPos, sourceSize );

                setRenderStates2DMode( color.getAlpha() < 255, true, useAlphaChannelOfTexture );

                vtx.push_back( S3DVertex(( f32 )poss.UpperLeftCorner.X, ( f32 )poss.UpperLeftCorner.Y, 0.0f,
                                         0.0f, 0.0f, 0.0f, color,
                                         tcoords.UpperLeftCorner.X, tcoords.UpperLeftCorner.Y ) );
                vtx.push_back( S3DVertex(( f32 )poss.LowerRightCorner.X, ( f32 )poss.UpperLeftCorner.Y, 0.0f,
                                         0.0f, 0.0f, 0.0f, color,
                                         tcoords.LowerRightCorner.X, tcoords.UpperLeftCorner.Y ) );
                vtx.push_back( S3DVertex(( f32 )poss.LowerRightCorner.X, ( f32 )poss.LowerRightCorner.Y, 0.0f,
                                         0.0f, 0.0f, 0.0f, color,
                                         tcoords.LowerRightCorner.X, tcoords.LowerRightCorner.Y ) );
                vtx.push_back( S3DVertex(( f32 )poss.UpperLeftCorner.X, ( f32 )poss.LowerRightCorner.Y, 0.0f,
                                         0.0f, 0.0f, 0.0f, color,
                                         tcoords.UpperLeftCorner.X, tcoords.LowerRightCorner.Y ) );

                const u32 curPos = vtx.size() - 4;
                indices.push_back( 0 + curPos );
                indices.push_back( 1 + curPos );
                indices.push_back( 2 + curPos );

                indices.push_back( 0 + curPos );
                indices.push_back( 2 + curPos );
                indices.push_back( 3 + curPos );
            }

            if ( vtx.size() )
            {
                drawVertexPrimitiveList2d3d( vtx.pointer(),
                                             vtx.size(), indices.pointer(),
                                             indices.size() / 3,
                                             EVT_STANDARD,
                                             scene::EPT_TRIANGLES,
                                             EIT_16BIT,
                                             false );
            }
        }


//! The same, but with a four element array of colors, one for each vertex
        void COGLES2Driver::draw2DImage( const video::ITexture* texture, const core::rect<s32>& destRect,
                                         const core::rect<s32>& sourceRect, const core::rect<s32>* clipRect,
                                         const video::SColor* const colors, bool useAlphaChannelOfTexture )
        {
            if ( !texture )
                return;

            // texcoords need to be flipped horizontally for RTTs
            const bool isRTT = texture->isRenderTarget();
            const core::dimension2du& ss = texture->getOriginalSize();
            const f32 invW = 1.f / static_cast<f32>( ss.Width );
            const f32 invH = 1.f / static_cast<f32>( ss.Height );
            const core::rect<f32> tcoords(
                sourceRect.UpperLeftCorner.X * invW,
                ( isRTT ? sourceRect.LowerRightCorner.Y : sourceRect.UpperLeftCorner.Y ) * invH,
                sourceRect.LowerRightCorner.X * invW,
                ( isRTT ? sourceRect.UpperLeftCorner.Y : sourceRect.LowerRightCorner.Y ) *invH );

            const video::SColor temp[4] =
            {
                0xFFFFFFFF,
                0xFFFFFFFF,
                0xFFFFFFFF,
                0xFFFFFFFF
            };

            const video::SColor* const useColor = colors ? colors : temp;

            disableTextures( 1 );
            setTexture( 0, texture );
            setRenderStates2DMode( useColor[0].getAlpha() < 255 || useColor[1].getAlpha() < 255 ||
                                   useColor[2].getAlpha() < 255 || useColor[3].getAlpha() < 255,
                                   true, useAlphaChannelOfTexture );

            if ( clipRect )
            {
                if ( !clipRect->isValid() )
                    return;

                glEnable( GL_SCISSOR_TEST );
                const core::dimension2d<u32>& renderTargetSize = getCurrentRenderTargetSize();
                glScissor( clipRect->UpperLeftCorner.X, renderTargetSize.Height - clipRect->LowerRightCorner.Y,
                           clipRect->getWidth(), clipRect->getHeight() );
            }

            u16 indices[] = {0, 1, 2, 3};
            S3DVertex vertices[4];
            vertices[0] = S3DVertex(( f32 )destRect.UpperLeftCorner.X, ( f32 )destRect.UpperLeftCorner.Y, 0, 0, 0, 1, useColor[0], tcoords.UpperLeftCorner.X, tcoords.UpperLeftCorner.Y );
            vertices[1] = S3DVertex(( f32 )destRect.LowerRightCorner.X, ( f32 )destRect.UpperLeftCorner.Y, 0, 0, 0, 1, useColor[3], tcoords.LowerRightCorner.X, tcoords.UpperLeftCorner.Y );
            vertices[2] = S3DVertex(( f32 )destRect.LowerRightCorner.X, ( f32 )destRect.LowerRightCorner.Y, 0, 0, 0, 1, useColor[2], tcoords.LowerRightCorner.X, tcoords.LowerRightCorner.Y );
            vertices[3] = S3DVertex(( f32 )destRect.UpperLeftCorner.X, ( f32 )destRect.LowerRightCorner.Y, 0, 0, 0, 1, useColor[1], tcoords.UpperLeftCorner.X, tcoords.LowerRightCorner.Y );
            drawVertexPrimitiveList2d3d( vertices, 4, indices, 2, video::EVT_STANDARD, scene::EPT_TRIANGLE_FAN, EIT_16BIT, false );

            if ( clipRect )
                glDisable( GL_SCISSOR_TEST );
            testGLError();
        }


//! draws a set of 2d images, using a color and the alpha channel
        void COGLES2Driver::draw2DImageBatch( const video::ITexture* texture,
                                         const core::position2d<s32>& pos,
                                         const core::array<core::rect<s32> >& sourceRects,
                                         const core::array<s32>& indices, s32 kerningWidth,
                                         const core::rect<s32>* clipRect, SColor color,
                                         bool useAlphaChannelOfTexture )
        {
            if ( !texture )
                return;

            disableTextures( 1 );
            if ( !setTexture( 0, texture ) )
                return;
            setRenderStates2DMode( color.getAlpha() < 255, true, useAlphaChannelOfTexture );

            if ( clipRect )
            {
                if ( !clipRect->isValid() )
                    return;

                glEnable( GL_SCISSOR_TEST );
                const core::dimension2d<u32>& renderTargetSize = getCurrentRenderTargetSize();
                glScissor( clipRect->UpperLeftCorner.X, renderTargetSize.Height - clipRect->LowerRightCorner.Y,
                           clipRect->getWidth(), clipRect->getHeight() );
            }

            const core::dimension2du& ss = texture->getOriginalSize();
            core::position2d<s32> targetPos( pos );
            // texcoords need to be flipped horizontally for RTTs
            const bool isRTT = texture->isRenderTarget();
            const f32 invW = 1.f / static_cast<f32>( ss.Width );
            const f32 invH = 1.f / static_cast<f32>( ss.Height );

            core::array<S3DVertex> vertices;
            core::array<u16> quadIndices;
            vertices.reallocate( indices.size()*4 );
            quadIndices.reallocate( indices.size()*3 );
            for ( u32 i = 0; i < indices.size(); ++i )
            {
                const s32 currentIndex = indices[i];
                if ( !sourceRects[currentIndex].isValid() )
                    break;

                const core::rect<f32> tcoords(
                    sourceRects[currentIndex].UpperLeftCorner.X * invW,
                    ( isRTT ? sourceRects[currentIndex].LowerRightCorner.Y : sourceRects[currentIndex].UpperLeftCorner.Y ) * invH,
                    sourceRects[currentIndex].LowerRightCorner.X * invW,
                    ( isRTT ? sourceRects[currentIndex].UpperLeftCorner.Y : sourceRects[currentIndex].LowerRightCorner.Y ) * invH );

                const core::rect<s32> poss( targetPos, sourceRects[currentIndex].getSize() );

                vertices.push_back( S3DVertex(( f32 )poss.UpperLeftCorner.X, ( f32 )poss.UpperLeftCorner.Y, 0, 0, 0, 1, color, tcoords.UpperLeftCorner.X, tcoords.UpperLeftCorner.Y ) );
                vertices.push_back( S3DVertex(( f32 )poss.LowerRightCorner.X, ( f32 )poss.UpperLeftCorner.Y, 0, 0, 0, 1, color, tcoords.LowerRightCorner.X, tcoords.UpperLeftCorner.Y ) );
                vertices.push_back( S3DVertex(( f32 )poss.LowerRightCorner.X, ( f32 )poss.LowerRightCorner.Y, 0, 0, 0, 1, color, tcoords.LowerRightCorner.X, tcoords.LowerRightCorner.Y ) );
                vertices.push_back( S3DVertex(( f32 )poss.UpperLeftCorner.X, ( f32 )poss.LowerRightCorner.Y, 0, 0, 0, 1, color, tcoords.UpperLeftCorner.X, tcoords.LowerRightCorner.Y ) );

                targetPos.X += sourceRects[currentIndex].getWidth();
            }
            drawVertexPrimitiveList2d3d( vertices.pointer(), 4, quadIndices.pointer(), 2*indices.size(), video::EVT_STANDARD, scene::EPT_TRIANGLES, EIT_16BIT, false );
            if ( clipRect )
                glDisable( GL_SCISSOR_TEST );
            testGLError();
        }


//! draw a 2d rectangle
        void COGLES2Driver::draw2DRectangle( SColor color, const core::rect<s32>& position,
                                             const core::rect<s32>* clip )
        {
            disableTextures();
            setRenderStates2DMode( color.getAlpha() < 255, false, false );

            core::rect<s32> pos = position;

            if ( clip )
                pos.clipAgainst( *clip );

            if ( !pos.isValid() )
                return;

            u16 indices[] = {0, 1, 2, 3};
            S3DVertex vertices[4];
            vertices[0] = S3DVertex(( f32 )pos.UpperLeftCorner.X, ( f32 )pos.UpperLeftCorner.Y, 0, 0, 0, 1, color, 0, 0 );
            vertices[1] = S3DVertex(( f32 )pos.LowerRightCorner.X, ( f32 )pos.UpperLeftCorner.Y, 0, 0, 0, 1, color, 0, 0 );
            vertices[2] = S3DVertex(( f32 )pos.LowerRightCorner.X, ( f32 )pos.LowerRightCorner.Y, 0, 0, 0, 1, color, 0, 0 );
            vertices[3] = S3DVertex(( f32 )pos.UpperLeftCorner.X, ( f32 )pos.LowerRightCorner.Y, 0, 0, 0, 1, color, 0, 0 );
            drawVertexPrimitiveList2d3d( vertices, 4, indices, 2, video::EVT_STANDARD, scene::EPT_TRIANGLE_FAN, EIT_16BIT, false );
        }


//! draw an 2d rectangle
        void COGLES2Driver::draw2DRectangle( const core::rect<s32>& position,
                                             SColor colorLeftUp, SColor colorRightUp, SColor colorLeftDown, SColor colorRightDown,
                                             const core::rect<s32>* clip )
        {
            core::rect<s32> pos = position;

            if ( clip )
                pos.clipAgainst( *clip );

            if ( !pos.isValid() )
                return;

            disableTextures();

            setRenderStates2DMode( colorLeftUp.getAlpha() < 255 ||
                                   colorRightUp.getAlpha() < 255 ||
                                   colorLeftDown.getAlpha() < 255 ||
                                   colorRightDown.getAlpha() < 255, false, false );

            u16 indices[] = {0, 1, 2, 3};
            S3DVertex vertices[4];
            vertices[0] = S3DVertex(( f32 )pos.UpperLeftCorner.X, ( f32 )pos.UpperLeftCorner.Y, 0, 0, 0, 1, colorLeftUp, 0, 0 );
            vertices[1] = S3DVertex(( f32 )pos.LowerRightCorner.X, ( f32 )pos.UpperLeftCorner.Y, 0, 0, 0, 1, colorRightUp, 0, 0 );
            vertices[2] = S3DVertex(( f32 )pos.LowerRightCorner.X, ( f32 )pos.LowerRightCorner.Y, 0, 0, 0, 1, colorRightDown, 0, 0 );
            vertices[3] = S3DVertex(( f32 )pos.UpperLeftCorner.X, ( f32 )pos.LowerRightCorner.Y, 0, 0, 0, 1, colorLeftDown, 0, 0 );
            drawVertexPrimitiveList2d3d( vertices, 4, indices, 2, video::EVT_STANDARD, scene::EPT_TRIANGLE_FAN, EIT_16BIT, false );
        }


//! Draws a 2d line.
        void COGLES2Driver::draw2DLine( const core::position2d<s32>& start,
                                        const core::position2d<s32>& end,
                                        SColor color )
        {
            disableTextures();
            setRenderStates2DMode( color.getAlpha() < 255, false, false );

            u16 indices[] = {0, 1};
            S3DVertex vertices[2];
            vertices[0] = S3DVertex(( f32 )start.X, ( f32 )start.Y, 0, 0, 0, 1, color, 0, 0 );
            vertices[1] = S3DVertex(( f32 )end.X, ( f32 )end.Y, 0, 0, 0, 1, color, 1, 1 );
            drawVertexPrimitiveList2d3d( vertices, 2, indices, 1, video::EVT_STANDARD, scene::EPT_LINES, EIT_16BIT, false );
        }



        bool COGLES2Driver::setTexture( u32 stage, const video::ITexture* texture )
        {
            if ( stage >= MaxTextureUnits )
                return false;

            if ( CurrentTexture[stage] == texture )
                return true;

            glActiveTexture( GL_TEXTURE0 + stage );

            CurrentTexture[stage] = texture;

            if ( !texture )
            {
                return true;
            }
            else
            {
                if ( texture->getDriverType() != EDT_OGLES2 )
                {
                    os::Printer::log( "Fatal Error: Tried to set a texture not owned by this driver.", ELL_ERROR );
                    return false;
                }
                glBindTexture( GL_TEXTURE_2D,
                               static_cast<const COGLES2Texture*>( texture )->getOGLES2TextureName() );
            }
            testGLError();
            return true;
        }


//! disables all textures beginning with the optional fromStage parameter.
        bool COGLES2Driver::disableTextures( u32 fromStage )
        {
            bool result = true;
            for ( u32 i = fromStage; i < MaxTextureUnits; ++i )
                result &= setTexture( i, 0 );
            return result;
        }


//! creates a matrix in supplied GLfloat array to pass to OGLES1
        inline void COGLES2Driver::createGLMatrix( float gl_matrix[16], const core::matrix4& m )
        {
            memcpy( gl_matrix, m.pointer(), 16 * sizeof( f32 ) );
        }


//! creates a opengltexturematrix from a D3D style texture matrix
        inline void COGLES2Driver::createGLTextureMatrix( float *o, const core::matrix4& m )
        {
            o[0] = m[0];
            o[1] = m[1];
            o[2] = 0.f;
            o[3] = 0.f;

            o[4] = m[4];
            o[5] = m[5];
            o[6] = 0.f;
            o[7] = 0.f;

            o[8] = 0.f;
            o[9] = 0.f;
            o[10] = 1.f;
            o[11] = 0.f;

            o[12] = m[8];
            o[13] = m[9];
            o[14] = 0.f;
            o[15] = 1.f;
        }


		//! returns a device dependent texture from a software surface (IImage)
        video::ITexture* COGLES2Driver::createDeviceDependentTexture(IImage* surface, const io::path& name, void* mipmapData)
        {
            return new COGLES2Texture( surface, name, this );
        }


		//! Sets a material.
        void COGLES2Driver::setMaterial( const SMaterial& material )
        {
            Material = material;
            OverrideMaterial.apply( Material );

            for ( s32 i = MaxTextureUnits - 1; i >= 0; --i )
            {
                setTexture( i, Material.getTexture( i ) );
                setTransform(( E_TRANSFORMATION_STATE )( ETS_TEXTURE_0 + i ),
                             Material.getTextureMatrix( i ) );
            }
        }

		//---srplab start
		SMaterial* COGLES2Driver::getMaterial()
		{
			return &Material;
		}
		//---srplab end

		//! prints error if an error happened.
        bool COGLES2Driver::testGLError()
        {
#ifdef _DEBUG
            GLenum g = glGetError();
            switch ( g )
            {
                case GL_NO_ERROR:
                    return false;
                case GL_INVALID_ENUM:
                    os::Printer::log( "GL_INVALID_ENUM", ELL_ERROR );
                    break;
                case GL_INVALID_VALUE:
                    os::Printer::log( "GL_INVALID_VALUE", ELL_ERROR );
                    break;
                case GL_INVALID_OPERATION:
                    os::Printer::log( "GL_INVALID_OPERATION", ELL_ERROR );
                    break;
                case GL_OUT_OF_MEMORY:
                    os::Printer::log( "GL_OUT_OF_MEMORY", ELL_ERROR );
                    break;
            };
            return true;
#else
            return false;
#endif
        }


        bool COGLES2Driver::testEGLError()
        {
#if defined(EGL_VERSION_1_0) && defined(_DEBUG)
            EGLint g = eglGetError();
            switch ( g )
            {
                case EGL_SUCCESS:
                    return false;
                case EGL_NOT_INITIALIZED :
                    os::Printer::log( "Not Initialized", ELL_ERROR );
                    break;
                case EGL_BAD_ACCESS:
                    os::Printer::log( "Bad Access", ELL_ERROR );
                    break;
                case EGL_BAD_ALLOC:
                    os::Printer::log( "Bad Alloc", ELL_ERROR );
                    break;
                case EGL_BAD_ATTRIBUTE:
                    os::Printer::log( "Bad Attribute", ELL_ERROR );
                    break;
                case EGL_BAD_CONTEXT:
                    os::Printer::log( "Bad Context", ELL_ERROR );
                    break;
                case EGL_BAD_CONFIG:
                    os::Printer::log( "Bad Config", ELL_ERROR );
                    break;
                case EGL_BAD_CURRENT_SURFACE:
                    os::Printer::log( "Bad Current Surface", ELL_ERROR );
                    break;
                case EGL_BAD_DISPLAY:
                    os::Printer::log( "Bad Display", ELL_ERROR );
                    break;
                case EGL_BAD_SURFACE:
                    os::Printer::log( "Bad Surface", ELL_ERROR );
                    break;
                case EGL_BAD_MATCH:
                    os::Printer::log( "Bad Match", ELL_ERROR );
                    break;
                case EGL_BAD_PARAMETER:
                    os::Printer::log( "Bad Parameter", ELL_ERROR );
                    break;
                case EGL_BAD_NATIVE_PIXMAP:
                    os::Printer::log( "Bad Native Pixmap", ELL_ERROR );
                    break;
                case EGL_BAD_NATIVE_WINDOW:
                    os::Printer::log( "Bad Native Window", ELL_ERROR );
                    break;
                case EGL_CONTEXT_LOST:
                    os::Printer::log( "Context Lost", ELL_ERROR );
                    break;
            };
            return true;
#else
            return false;
#endif
        }


		//! sets the needed renderstates
        void COGLES2Driver::setRenderStates3DMode()
        {
            if ( CurrentRenderMode != ERM_3D )
            {
                // Reset Texture Stages
                if ( BlendEnabled )
                {
                    glDisable( GL_BLEND );
                    BlendEnabled = false;
                }

                ResetRenderStates = true;
            }

            if ( ResetRenderStates || LastMaterial != Material )
            {
                // unset old material

                if ( LastMaterial.MaterialType != Material.MaterialType &&
                     static_cast<u32>( LastMaterial.MaterialType ) < MaterialRenderers.size() )
                    MaterialRenderers[LastMaterial.MaterialType].Renderer->OnUnsetMaterial();

                // set new material.
                if ( static_cast<u32>( Material.MaterialType ) < MaterialRenderers.size() )
                    MaterialRenderers[Material.MaterialType].Renderer->OnSetMaterial(
                        Material, LastMaterial, ResetRenderStates, this );

                LastMaterial = Material;
                ResetRenderStates = false;
            }

            if ( static_cast<u32>( Material.MaterialType ) < MaterialRenderers.size() )
                MaterialRenderers[Material.MaterialType].Renderer->OnRender( this, video::EVT_STANDARD );

            testGLError();

            CurrentRenderMode = ERM_3D;
        }


		GLint COGLES2Driver::getTextureWrapMode(u8 clamp) const
		{
			switch (clamp)
			{
				case ETC_CLAMP:
					//  mode=GL_CLAMP; not supported in ogl-es
					return GL_CLAMP_TO_EDGE;
				case ETC_CLAMP_TO_EDGE:
					return GL_CLAMP_TO_EDGE;
				case ETC_CLAMP_TO_BORDER:
					//  mode=GL_CLAMP_TO_BORDER; not supported in ogl-es
					return GL_CLAMP_TO_EDGE;
				case ETC_MIRROR:
	#ifdef GL_OES_texture_mirrored_repeat
					if ( FeatureAvailable[IRR_OES_texture_mirrored_repeat] )
						return GL_MIRRORED_REPEAT_OES;
					else
	#endif
						return GL_REPEAT;
				default:
					return GL_REPEAT;
			}
        }


        void COGLES2Driver::setWrapMode( const SMaterial& material )
        {
            testGLError();
            // texture address mode
            // Has to be checked always because it depends on the textures
            for ( u32 u = 0; u < MaxTextureUnits; ++u )
            {
				if (MultiTextureExtension)
					glActiveTexture(GL_TEXTURE0 + u);
				else if (u>0)
					break; // stop loop

				// the APPLE npot restricted extension needs some care as it only supports CLAMP_TO_EDGE
				if (queryFeature(EVDF_TEXTURE_NPOT) && !FeatureAvailable[IRR_OES_texture_npot] &&
						CurrentTexture[u] && (CurrentTexture[u]->getSize() != CurrentTexture[u]->getOriginalSize()))
				{
					glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
					glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
				}
				else
				{
					glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, getTextureWrapMode(material.TextureLayer[u].TextureWrapU));
					glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, getTextureWrapMode(material.TextureLayer[u].TextureWrapV));
				}
			}
		}


		//! Can be called by an IMaterialRenderer to make its work easier.
        void COGLES2Driver::setBasicRenderStates( const SMaterial& material, const SMaterial& lastmaterial,
                                                  bool resetAllRenderStates )
        {
            testGLError();
            // Texture filter
            // Has to be checked always because it depends on the textures
            // Filtering has to be set for each texture layer
            for ( u32 i = 0; i < MaxTextureUnits; ++i )
            {
                //Thibault : strange Blue artifact on textures in exemple 02
                glActiveTexture( GL_TEXTURE0 + i );

                if ( material.TextureLayer[i].BilinearFilter || material.TextureLayer[i].TrilinearFilter )
                {
                    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
                }
                else
                {
                    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );
                }

                if ( material.getTexture( i ) && material.getTexture( i )->hasMipMaps() )
                {
                    if ( material.TextureLayer[i].TrilinearFilter )
                    {
                        glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR );
                    }
                    else if ( material.TextureLayer[i].BilinearFilter )
                    {
                        glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST );
                    }
                    else
                    {
                        glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST );
                    }
                }
                else if ( material.TextureLayer[i].BilinearFilter || material.TextureLayer[i].TrilinearFilter )
                {
                    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
                }
                else
                {
                    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
                }

#ifdef GL_EXT_texture_filter_anisotropic
                if ( FeatureAvailable[IRR_EXT_texture_filter_anisotropic] )
                    glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT,
                                     static_cast<GLfloat>( material.TextureLayer[i].AnisotropicFilter > 1 ? core::min_( MaxAnisotropy, material.TextureLayer[i].AnisotropicFilter ) : 1 ) );
#endif
            }
            testGLError();

// TODO ogl-es
            // fillmode
//  if (resetAllRenderStates || (lastmaterial.Wireframe != material.Wireframe) || (lastmaterial.PointCloud != material.PointCloud))
//      glPolygonMode(GL_FRONT_AND_BACK, material.Wireframe ? GL_LINE : material.PointCloud? GL_POINT : GL_FILL);

            // shademode
            if ( resetAllRenderStates || ( lastmaterial.GouraudShading != material.GouraudShading ) )
            {
                //TODO : OpenGL ES 2.0 Port glShadeModel
                //if (material.GouraudShading)
                //  glShadeModel(GL_SMOOTH);
                //else
                //  glShadeModel(GL_FLAT);
            }
            testGLError();

            // zbuffer
            if ( resetAllRenderStates || lastmaterial.ZBuffer != material.ZBuffer )
            {
                switch ( material.ZBuffer )
                {
                    case ECFN_NEVER:
                        glDisable( GL_DEPTH_TEST );
                        break;
                    case ECFN_LESSEQUAL:
                        glEnable( GL_DEPTH_TEST );
                        glDepthFunc( GL_LEQUAL );
                        break;
                    case ECFN_EQUAL:
                        glEnable( GL_DEPTH_TEST );
                        glDepthFunc( GL_EQUAL );
                        break;
                    case ECFN_LESS:
                        glEnable( GL_DEPTH_TEST );
                        glDepthFunc( GL_LESS );
                        break;
                    case ECFN_NOTEQUAL:
                        glEnable( GL_DEPTH_TEST );
                        glDepthFunc( GL_NOTEQUAL );
                        break;
                    case ECFN_GREATEREQUAL:
                        glEnable( GL_DEPTH_TEST );
                        glDepthFunc( GL_GEQUAL );
                        break;
                    case ECFN_GREATER:
                        glEnable( GL_DEPTH_TEST );
                        glDepthFunc( GL_GREATER );
                        break;
                    case ECFN_ALWAYS:
                        glEnable( GL_DEPTH_TEST );
                        glDepthFunc( GL_ALWAYS );
                        break;
                }
            }
            testGLError();

            // zwrite
//  if (resetAllRenderStates || lastmaterial.ZWriteEnable != material.ZWriteEnable)
            {
                if ( material.ZWriteEnable && ( AllowZWriteOnTransparent || !material.isTransparent() ) )
                {
                    glDepthMask( GL_TRUE );
                }
                else
                    glDepthMask( GL_FALSE );
            }

            // back face culling
            if ( resetAllRenderStates || ( lastmaterial.FrontfaceCulling != material.FrontfaceCulling ) || ( lastmaterial.BackfaceCulling != material.BackfaceCulling ) )
            {
                if (( material.FrontfaceCulling ) && ( material.BackfaceCulling ) )
                {
                    glCullFace( GL_FRONT_AND_BACK );
                    glEnable( GL_CULL_FACE );
                }
                else if ( material.BackfaceCulling )
                {
                    glCullFace( GL_BACK );
                    glEnable( GL_CULL_FACE );
                }
                else if ( material.FrontfaceCulling )
                {
                    glCullFace( GL_FRONT );
                    glEnable( GL_CULL_FACE );
                }
                else
                    glDisable( GL_CULL_FACE );
            }
            testGLError();

            // Color Mask
            if ( resetAllRenderStates || lastmaterial.ColorMask != material.ColorMask )
            {
                glColorMask(
                    ( material.ColorMask & ECP_RED ) ? GL_TRUE : GL_FALSE,
                    ( material.ColorMask & ECP_GREEN ) ? GL_TRUE : GL_FALSE,
                    ( material.ColorMask & ECP_BLUE ) ? GL_TRUE : GL_FALSE,
                    ( material.ColorMask & ECP_ALPHA ) ? GL_TRUE : GL_FALSE );
            }
            testGLError();

            // thickness
            if ( resetAllRenderStates || lastmaterial.Thickness != material.Thickness )
            {
                //TODO : OpenGL ES 2.0 Port glPointSize
                //glPointSize(material.Thickness);
                glLineWidth( material.Thickness == 0 ? 1 : material.Thickness );
                //glLineWidth with 0 generate GL_INVALID_VALUE on real hardware.
            }
            testGLError();

            // Anti aliasing
            if ( resetAllRenderStates || lastmaterial.AntiAliasing != material.AntiAliasing )
            {
//      if (FeatureAvailable[IRR_ARB_multisample])
                {
                    if ( material.AntiAliasing & EAAM_ALPHA_TO_COVERAGE )
                        glEnable( GL_SAMPLE_ALPHA_TO_COVERAGE );
                    else if ( lastmaterial.AntiAliasing & EAAM_ALPHA_TO_COVERAGE )
                        glDisable( GL_SAMPLE_ALPHA_TO_COVERAGE );

                    //TODO : OpenGL ES 2.0 Port GL_MULTISAMPLE
                    //if ((AntiAlias >= 2) && (material.AntiAliasing & (EAAM_SIMPLE|EAAM_QUALITY)))
                    //  glEnable(GL_MULTISAMPLE);
                    //else
                    //  glDisable(GL_MULTISAMPLE);
                }
                if ( AntiAlias >= 2 )
                {
                    //TODO : OpenGL ES 2.0 Port GL_LINE_SMOOTH
                    //if (material.AntiAliasing & EAAM_LINE_SMOOTH)
                    //  glEnable(GL_LINE_SMOOTH);
                    //else if (lastmaterial.AntiAliasing & EAAM_LINE_SMOOTH)
                    //  glDisable(GL_LINE_SMOOTH);
                    //if (material.AntiAliasing & EAAM_POINT_SMOOTH)
                    //  // often in software, and thus very slow
                    //  glEnable(GL_POINT_SMOOTH);
                    //else if (lastmaterial.AntiAliasing & EAAM_POINT_SMOOTH)
                    //  glDisable(GL_POINT_SMOOTH);
                }
            }
            testGLError();

            setWrapMode( material );

            //Thibault : Strange blue Artifact in exemple 01
            glActiveTexture( GL_TEXTURE0 );
            testGLError();
        }


//! sets the needed renderstates
        void COGLES2Driver::setRenderStates2DMode( bool alpha, bool texture, bool alphaChannel )
        {
            if ( CurrentRenderMode != ERM_2D || Transformation3DChanged )
            {
                // unset last 3d material
                if ( CurrentRenderMode == ERM_3D )
                {
                    if ( static_cast<u32>( LastMaterial.MaterialType ) < MaterialRenderers.size() )
                        MaterialRenderers[LastMaterial.MaterialType].Renderer->OnUnsetMaterial();
                    SMaterial mat;
                    mat.ZBuffer = ECFN_NEVER;
                    mat.Lighting = false;
                    mat.TextureLayer[0].BilinearFilter = false;
                    mat.ColorMaterial = 0;
                    setBasicRenderStates( mat, mat, true );
                    LastMaterial = mat;
                }

                TwoDRenderer->useProgram(); //Fixed Pipeline Shader needed to render 2D

                const core::dimension2d<u32>& renderTargetSize = getCurrentRenderTargetSize();
                core::matrix4 m;
                m.buildProjectionMatrixOrthoLH( f32( renderTargetSize.Width ), f32( -( s32 )( renderTargetSize.Height ) ), -1.0, 1.0 );
                m.setTranslation( core::vector3df( -1, 1, 0 ) );

                TwoDRenderer->setOrthoMatrix( m );

                Transformation3DChanged = false;
            }

            if ( alphaChannel || alpha )
            {
                if ( ! BlendEnabled )
                {
                    glEnable( GL_BLEND );
                    BlendEnabled = true;
                }
                blendFunc( EBF_SRC_ALPHA, EBF_ONE_MINUS_SRC_ALPHA );
                TwoDRenderer->useAlphaTest( true );
                TwoDRenderer->setAlphaTestValue( 0.f );
            }
            else
            {
                if ( BlendEnabled )
                {
                    glDisable( GL_BLEND );
                    BlendEnabled = false;
                }
                TwoDRenderer->useAlphaTest( false );
            }

            if ( texture )
            {
                glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
                glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );

                glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT );
                glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT );

                TwoDRenderer->useTexture( true );
            }
            else
                TwoDRenderer->useTexture( false );

            CurrentRenderMode = ERM_2D;
            testGLError();
        }


		//! \return Returns the name of the video driver.
        const wchar_t* COGLES2Driver::getName() const
        {
            return Name.c_str();
        }


		//! deletes all dynamic lights there are
        void COGLES2Driver::deleteAllDynamicLights()
        {
            RequestedLights.clear();
            CNullDriver::deleteAllDynamicLights();
        }


		//! adds a dynamic light
        s32 COGLES2Driver::addDynamicLight( const SLight& light )
        {
            CNullDriver::addDynamicLight( light );

            RequestedLights.push_back( RequestedLight( light ) );

            u32 newLightIndex = RequestedLights.size() - 1;

            return ( s32 )newLightIndex;
        }

		//! Turns a dynamic light on or off
		//! \param lightIndex: the index returned by addDynamicLight
		//! \param turnOn: true to turn the light on, false to turn it off
        void COGLES2Driver::turnLightOn( s32 lightIndex, bool turnOn )
        {
            if ( lightIndex < 0 || lightIndex >= ( s32 )RequestedLights.size() )
                return;

            RequestedLight & requestedLight = RequestedLights[lightIndex];
            requestedLight.DesireToBeOn = turnOn;
        }


		//! returns the maximal amount of dynamic lights the device can handle
        u32 COGLES2Driver::getMaximalDynamicLightAmount() const
        {
            return MaxLights;
        }


		//! Sets the dynamic ambient light color.
        void COGLES2Driver::setAmbientLight( const SColorf& color )
        {
            AmbientLight = color;
        }

		//! returns the dynamic ambient light color.
        const SColorf& COGLES2Driver::getAmbientLight() const
        {
            return AmbientLight;
        }

		// this code was sent in by Oliver Klems, thank you
        void COGLES2Driver::setViewPort( const core::rect<s32>& area )
        {
            core::rect<s32> vp = area;
            core::rect<s32> rendert( 0, 0, getCurrentRenderTargetSize().Width, getCurrentRenderTargetSize().Height );
            vp.clipAgainst( rendert );

            if ( vp.getHeight() > 0 && vp.getWidth() > 0 )
                glViewport( vp.UpperLeftCorner.X,
                            getCurrentRenderTargetSize().Height - vp.UpperLeftCorner.Y - vp.getHeight(),
                            vp.getWidth(), vp.getHeight() );

            ViewPort = vp;
            testGLError();
        }


		//! Draws a shadow volume into the stencil buffer.
        void COGLES2Driver::drawStencilShadowVolume( const core::vector3df* triangles, s32 count, bool zfail )
        {
            if ( !StencilBuffer || !count )
                return;

            // unset last 3d material
            if ( CurrentRenderMode == ERM_3D &&
                 static_cast<u32>( Material.MaterialType ) < MaterialRenderers.size() )
            {
                MaterialRenderers[Material.MaterialType].Renderer->OnUnsetMaterial();
                ResetRenderStates = true;
            }

            // store current OGLES state
            const GLboolean cullFaceEnabled = glIsEnabled( GL_CULL_FACE );
            GLint cullFaceMode;
            glGetIntegerv( GL_CULL_FACE_MODE, &cullFaceMode );
            GLint depthFunc;
            glGetIntegerv( GL_DEPTH_FUNC, &depthFunc );
            GLboolean depthMask;
            glGetBooleanv( GL_DEPTH_WRITEMASK, &depthMask );

            glDepthFunc( GL_LEQUAL );
            glDepthMask( GL_FALSE ); // no depth buffer writing
            glColorMask( GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE ); // no color buffer drawing
            glEnable( GL_STENCIL_TEST );
            glEnable( GL_POLYGON_OFFSET_FILL );
            glPolygonOffset( 0.0f, 1.0f );

            glEnableVertexAttribArray( EVA_POSITION );

            glVertexAttribPointer( EVA_POSITION, 3, GL_FLOAT, false, sizeof( core::vector3df ), &triangles[0] );
            glStencilMask( ~0 );
            glStencilFunc( GL_ALWAYS, 0, ~0 );

            GLenum decr = GL_DECR;
            GLenum incr = GL_INCR;
#if defined(GL_OES_stencil_wrap)
            if ( FeatureAvailable[IRR_OES_stencil_wrap] )
            {
                decr = GL_DECR_WRAP_OES;
                incr = GL_INCR_WRAP_OES;
            }
#endif
            glEnable( GL_CULL_FACE );
            if ( !zfail )
            {
                // ZPASS Method

                glCullFace( GL_BACK );
                glStencilOp( GL_KEEP, GL_KEEP, incr );
                glDrawArrays( GL_TRIANGLES, 0, count );

                glCullFace( GL_FRONT );
                glStencilOp( GL_KEEP, GL_KEEP, decr );
                glDrawArrays( GL_TRIANGLES, 0, count );
            }
            else
            {
                // ZFAIL Method

                glStencilOp( GL_KEEP, incr, GL_KEEP );
                glCullFace( GL_FRONT );
                glDrawArrays( GL_TRIANGLES, 0, count );

                glStencilOp( GL_KEEP, decr, GL_KEEP );
                glCullFace( GL_BACK );
                glDrawArrays( GL_TRIANGLES, 0, count );
            }

            glDisableVertexAttribArray( EVA_POSITION );
            glColorMask( GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE );
            glDisable( GL_STENCIL_TEST );
            if ( cullFaceEnabled )
                glEnable( GL_CULL_FACE );
            else
                glDisable( GL_CULL_FACE );
            glCullFace( cullFaceMode );
            glDepthFunc( depthFunc );
            glDepthMask( depthMask );
            testGLError();
        }


        void COGLES2Driver::drawStencilShadow( bool clearStencilBuffer, video::SColor leftUpEdge,
                                               video::SColor rightUpEdge, video::SColor leftDownEdge, video::SColor rightDownEdge )
        {
            if ( !StencilBuffer )
                return;

            disableTextures();

            // store attributes
            GLboolean depthMask;
            glGetBooleanv( GL_DEPTH_WRITEMASK, &depthMask );
//            GLint shadeModel;
            //TODO : OpenGL ES 2.0 Port glGetIntegerv
            //glGetIntegerv(GL_SHADE_MODEL, &shadeModel);
//            GLint blendSrc, blendDst;
            //TODO : OpenGL ES 2.0 Port glGetIntegerv
            //glGetIntegerv(GL_BLEND_SRC, &blendSrc);
            //glGetIntegerv(GL_BLEND_DST, &blendDst);


            glDepthMask( GL_FALSE );

            //TODO : OpenGL ES 2.0 Port glShadeModel
            //glShadeModel( GL_FLAT );
            glColorMask( GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE );

            if ( ! BlendEnabled )
                glEnable( GL_BLEND );

            glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );

            glEnable( GL_STENCIL_TEST );
            glStencilFunc( GL_NOTEQUAL, 0, ~0 );
            glStencilOp( GL_KEEP, GL_KEEP, GL_KEEP );

            // draw a shadow rectangle covering the entire screen using stencil buffer
            //Wrapper->glMatrixMode(GL_MODELVIEW);
            //TODO : OpenGL ES 2.0 Port glPushMatrix
            //glPushMatrix();
            //Wrapper->glLoadIdentity();
            //Wrapper->glMatrixMode(GL_PROJECTION);
            //TODO : OpenGL ES 2.0 Port glPushMatrix
            //glPushMatrix();
            //Wrapper->glLoadIdentity();

            u16 indices[] = {0, 1, 2, 3};
            S3DVertex vertices[4];
            vertices[0] = S3DVertex( -1.f, -1.f, 0.9f, 0, 0, 1, leftDownEdge, 0, 0 );
            vertices[1] = S3DVertex( -1.f, 1.f, 0.9f, 0, 0, 1, leftUpEdge, 0, 0 );
            vertices[2] = S3DVertex( 1.f, 1.f, 0.9f, 0, 0, 1, rightUpEdge, 0, 0 );
            vertices[3] = S3DVertex( 1.f, -1.f, 0.9f, 0, 0, 1, rightDownEdge, 0, 0 );
            drawVertexPrimitiveList2d3d( vertices, 4, indices, 2, video::EVT_STANDARD, scene::EPT_TRIANGLE_FAN, EIT_16BIT, false );

            if ( clearStencilBuffer )
                glClear( GL_STENCIL_BUFFER_BIT );

            // restore settings
            //TODO : OpenGL ES 2.0 Port glPopMatrix
            //glPopMatrix();
            //Wrapper->glMatrixMode(GL_MODELVIEW);
            //TODO : OpenGL ES 2.0 Port glPopMatrix
            //glPopMatrix();
            glDisable( GL_STENCIL_TEST );

            glDepthMask( depthMask );
            //TODO : OpenGL ES 2.0 Port glShadeModel
            //glShadeModel(shadeModel);
            if ( ! BlendEnabled )
                glDisable( GL_BLEND );
            //TODO :
            //glBlendFunc(blendSrc, blendDst);
            testGLError();
        }


		//! Draws a 3d line.
        void COGLES2Driver::draw3DLine( const core::vector3df& start,
                                        const core::vector3df& end, SColor color )
        {
            setRenderStates3DMode();

            u16 indices[] = {0, 1};
            S3DVertex vertices[2];
            vertices[0] = S3DVertex( start.X, start.Y, start.Z, 0, 0, 1, color, 0, 0 );
            vertices[1] = S3DVertex( end.X, end.Y, end.Z, 0, 0, 1, color, 0, 0 );
            drawVertexPrimitiveList2d3d( vertices, 2, indices, 1, video::EVT_STANDARD, scene::EPT_LINES );
        }


//! Only used by the internal engine. Used to notify the driver that
//! the window was resized.
        void COGLES2Driver::OnResize( const core::dimension2d<u32>& size )
        {
            CNullDriver::OnResize( size );
            glViewport( 0, 0, size.Width, size.Height );
            testGLError();
        }


//! Returns type of video driver
        E_DRIVER_TYPE COGLES2Driver::getDriverType() const
        {
            return EDT_OGLES2;
        }


//! returns color format
        ECOLOR_FORMAT COGLES2Driver::getColorFormat() const
        {
            return ColorFormat;
        }


//! Sets a vertex shader constant.
        void COGLES2Driver::setVertexShaderConstant( const f32* data, s32 startRegister, s32 constantAmount )
        {
#ifdef GL_vertex_program
            for ( s32 i = 0; i < constantAmount; ++i )
                glProgramLocalParameter4fv( GL_VERTEX_PROGRAM, startRegister + i, &data[i*4] );
#endif
        }

//! Sets a pixel shader constant.
        void COGLES2Driver::setPixelShaderConstant( const f32* data, s32 startRegister, s32 constantAmount )
        {
#ifdef GL_fragment_program
            for ( s32 i = 0; i < constantAmount; ++i )
                glProgramLocalParameter4fv( GL_FRAGMENT_PROGRAM, startRegister + i, &data[i*4] );
#endif
        }

//! Sets a constant for the vertex shader based on a name.
        bool COGLES2Driver::setVertexShaderConstant( const c8* name, const f32* floats, int count )
        {
            //pass this along, as in GLSL the same routine is used for both vertex and fragment shaders
            return setPixelShaderConstant( name, floats, count );
        }

//! Sets a constant for the pixel shader based on a name.
        bool COGLES2Driver::setPixelShaderConstant( const c8* name, const f32* floats, int count )
        {
            os::Printer::log( "Error: Please call services->setPixelShaderConstant(), not VideoDriver->setPixelShaderConstant()." );
            return false;
        }

//! Sets a vertex pointer the vertex shader based on a name.
        bool COGLES2Driver::setVertexShaderPointer( const c8*, const void*, s32, bool, u16 )
        {
            os::Printer::log( "Error: Please call services->setVertexPointer(), not VideoDriver->setVertexPointer()." );
            return false;
        }



//! Adds a new material renderer to the VideoDriver, using pixel and/or
//! vertex shaders to render geometry.
        s32 COGLES2Driver::addShaderMaterial( const c8* vertexShaderProgram,
                                              const c8* pixelShaderProgram,
                                              IShaderConstantSetCallBack* callback,
                                              E_MATERIAL_TYPE baseMaterial, s32 userData )
        {
            os::Printer::log( "No shader support." );
            return -1;
        }


//! Adds a new material renderer to the VideoDriver, using GLSL to render geometry.
        s32 COGLES2Driver::addHighLevelShaderMaterial(
				const c8* vertexShaderProgram,
				const c8* vertexShaderEntryPointName,
				E_VERTEX_SHADER_TYPE vsCompileTarget,
				const c8* pixelShaderProgram,
				const c8* pixelShaderEntryPointName,
				E_PIXEL_SHADER_TYPE psCompileTarget,
				const c8* geometryShaderProgram,
				const c8* geometryShaderEntryPointName,
				E_GEOMETRY_SHADER_TYPE gsCompileTarget,
				scene::E_PRIMITIVE_TYPE inType,
				scene::E_PRIMITIVE_TYPE outType,
				u32 verticesOut,
				IShaderConstantSetCallBack* callback,
				E_MATERIAL_TYPE baseMaterial,
				s32 userData)
        {
            s32 nr = -1;
            COGLES2SLMaterialRenderer* r = new COGLES2SLMaterialRenderer(
                this, nr, vertexShaderProgram,
                pixelShaderProgram,
                callback, getMaterialRenderer( baseMaterial ), userData );

            r->drop();
            return nr;
        }

//! Returns a pointer to the IVideoDriver interface. (Implementation for
//! IMaterialRendererServices)
        IVideoDriver* COGLES2Driver::getVideoDriver()
        {
            return this;
        }


//! Returns pointer to the IGPUProgrammingServices interface.
        IGPUProgrammingServices* COGLES2Driver::getGPUProgrammingServices()
        {
            return this;
        }


        ITexture* COGLES2Driver::addRenderTargetTexture( const core::dimension2d<u32>& size,
                                                         const io::path& name,
                                                         const ECOLOR_FORMAT format )
        {
            //disable mip-mapping
            const bool generateMipLevels = getTextureCreationFlag( ETCF_CREATE_MIP_MAPS );
            setTextureCreationFlag( ETCF_CREATE_MIP_MAPS, false );

            video::ITexture* rtt = 0;

#if defined(GL_OES_framebuffer_object)
            // if driver supports FrameBufferObjects, use them
            if ( queryFeature( EVDF_FRAMEBUFFER_OBJECT ) )
            {
                rtt = new COGLES2FBOTexture( size, name, this, format );
                if ( rtt )
                {
                    addTexture( rtt );
                    ITexture* tex = createDepthTexture( rtt );
                    if ( tex )
                    {
                        static_cast<video::COGLES2FBODepthTexture*>( tex )->attach( rtt );
                        tex->drop();
                    }
                    rtt->drop();
                }
            }
            else
#endif
            {
                // the simple texture is only possible for size <= screensize
                // we try to find an optimal size with the original constraints
                core::dimension2du destSize( core::min_( size.Width, ScreenSize.Width ), core::min_( size.Height, ScreenSize.Height ) );
                destSize = destSize.getOptimalSize(( size == size.getOptimalSize() ), false, false );
                rtt = addTexture( destSize, name, ECF_A8R8G8B8 );
                if ( rtt )
                    static_cast<video::COGLES2Texture*>( rtt )->setIsRenderTarget( true );
            }

            //restore mip-mapping
            setTextureCreationFlag( ETCF_CREATE_MIP_MAPS, generateMipLevels );

            return rtt;
        }


//! Returns the maximum amount of primitives
        u32 COGLES2Driver::getMaximalPrimitiveCount() const
        {
            return 65535;
        }


//! set or reset render target
        bool COGLES2Driver::setRenderTarget( video::ITexture* texture, bool clearBackBuffer,
                                             bool clearZBuffer, SColor color )
        {
            // check for right driver type

            if ( texture && texture->getDriverType() != EDT_OGLES2 )
            {
                os::Printer::log( "Fatal Error: Tried to set a texture not owned by this driver.", ELL_ERROR );
                return false;
            }

            // check if we should set the previous RT back

            setTexture( 0, 0 );
            ResetRenderStates = true;
            if ( RenderTargetTexture != 0 )
            {
                RenderTargetTexture->unbindRTT();
            }

            if ( texture )
            {
                // we want to set a new target. so do this.
                RenderTargetTexture = static_cast<COGLES2Texture*>( texture );
                RenderTargetTexture->bindRTT();
                CurrentRendertargetSize = texture->getSize();
            }
            else
            {
                glViewport( 0, 0, ScreenSize.Width, ScreenSize.Height );
                RenderTargetTexture = 0;
                CurrentRendertargetSize = core::dimension2d<u32>( 0, 0 );
            }

            GLbitfield mask = 0;
            if ( clearBackBuffer )
            {
                const f32 inv = 1.0f / 255.0f;
                glClearColor( color.getRed() * inv, color.getGreen() * inv,
                              color.getBlue() * inv, color.getAlpha() * inv );

                mask |= GL_COLOR_BUFFER_BIT;
            }
            if ( clearZBuffer )
            {
                glDepthMask( GL_TRUE );
                LastMaterial.ZWriteEnable = true;
                mask |= GL_DEPTH_BUFFER_BIT;
            }

            glClear( mask );
            testGLError();

            return true;
        }


// returns the current size of the screen or rendertarget
        const core::dimension2d<u32>& COGLES2Driver::getCurrentRenderTargetSize() const
        {
            if ( CurrentRendertargetSize.Width == 0 )
                return ScreenSize;
            else
                return CurrentRendertargetSize;
        }


//! Clears the ZBuffer.
        void COGLES2Driver::clearZBuffer()
        {
            GLboolean enabled = GL_TRUE;
            glGetBooleanv( GL_DEPTH_WRITEMASK, &enabled );

            glDepthMask( GL_TRUE );
            glClear( GL_DEPTH_BUFFER_BIT );

            glDepthMask( enabled );
            testGLError();
        }


//! Returns an image created from the last rendered frame.
// We want to read the front buffer to get the latest render finished.
// This is not possible under ogl-es, though, so one has to call this method
// outside of the render loop only.
        IImage* COGLES2Driver::createScreenShot()
        {
            int format = GL_RGBA;
            int type = GL_UNSIGNED_BYTE;
            if ( FeatureAvailable[IRR_IMG_read_format] || FeatureAvailable[IRR_OES_read_format] )
            {
#ifdef GL_IMPLEMENTATION_COLOR_READ_TYPE_OES
                glGetIntegerv( GL_IMPLEMENTATION_COLOR_READ_FORMAT_OES, &format );
                glGetIntegerv( GL_IMPLEMENTATION_COLOR_READ_TYPE_OES, &type );
#endif
                // there's a format we don't support ATM
                if ( GL_UNSIGNED_SHORT_4_4_4_4 == type )
                    type = GL_UNSIGNED_SHORT_5_5_5_1;
            }

            IImage* newImage = 0;
            if ( GL_RGBA == format )
            {
                if ( GL_UNSIGNED_BYTE == type )
                    newImage = new CImage( ECF_A8R8G8B8, ScreenSize );
                else
                    newImage = new CImage( ECF_A1R5G5B5, ScreenSize );
            }
            else
            {
                if ( GL_UNSIGNED_BYTE == type )
                    newImage = new CImage( ECF_R8G8B8, ScreenSize );
                else
                    newImage = new CImage( ECF_R5G6B5, ScreenSize );
            }

            u8* pixels = static_cast<u8*>( newImage->lock() );
            if ( !pixels )
            {
                newImage->drop();
                return 0;
            }

            glReadPixels( 0, 0, ScreenSize.Width, ScreenSize.Height, format, type, pixels );

            // opengl images are horizontally flipped, so we have to fix that here.
            const s32 pitch = newImage->getPitch();
            u8* p2 = pixels + ( ScreenSize.Height - 1 ) * pitch;
            u8* tmpBuffer = new u8[pitch];
            for ( u32 i = 0; i < ScreenSize.Height; i += 2 )
            {
                memcpy( tmpBuffer, pixels, pitch );
                memcpy( pixels, p2, pitch );
                memcpy( p2, tmpBuffer, pitch );
                pixels += pitch;
                p2 -= pitch;
            }
            delete [] tmpBuffer;

            newImage->unlock();

            if ( testGLError() )
            {
                newImage->drop();
                return 0;
            }
            testGLError();
            return newImage;
        }


//! get depth texture for the given render target texture
        ITexture* COGLES2Driver::createDepthTexture( ITexture* texture, bool shared )
        {
            if (( texture->getDriverType() != EDT_OGLES2 ) || ( !texture->isRenderTarget() ) )
                return 0;
            COGLES2Texture* tex = static_cast<COGLES2Texture*>( texture );

            if ( !tex->isFrameBufferObject() )
                return 0;

            if ( shared )
            {
                for ( u32 i = 0; i < DepthTextures.size(); ++i )
                {
                    if ( DepthTextures[i]->getSize() == texture->getSize() )
                    {
                        DepthTextures[i]->grab();
                        return DepthTextures[i];
                    }
                }
                DepthTextures.push_back( new COGLES2FBODepthTexture( texture->getSize(), "depth1", this ) );
                return DepthTextures.getLast();
            }
            return ( new COGLES2FBODepthTexture( texture->getSize(), "depth1", this ) );
        }


        void COGLES2Driver::removeDepthTexture( ITexture* texture )
        {
            for ( u32 i = 0; i < DepthTextures.size(); ++i )
            {
                if ( texture == DepthTextures[i] )
                {
                    DepthTextures.erase( i );
                    return;
                }
            }
        }

        void COGLES2Driver::reloadShaders()
        {
            FixedPipeline->reload();
        }

        void COGLES2Driver::deleteFramebuffers( s32 n, const u32 *framebuffers )
        {
            glDeleteFramebuffers( n, framebuffers );
        }

        void COGLES2Driver::deleteRenderbuffers( s32 n, const u32 *renderbuffers )
        {
            glDeleteRenderbuffers( n, renderbuffers );
        }

        void COGLES2Driver::enableBlend()
        {
            if ( ! BlendEnabled )
            {
                BlendEnabled = true;
                glEnable( GL_BLEND );
            }
        }

        void COGLES2Driver::disableBlend()
        {
            if ( BlendEnabled )
            {
                BlendEnabled = false;
                glDisable( GL_BLEND );
            }
        }

        u32 getGLBlend( E_BLEND_FACTOR factor )
        {
            u32 r = 0;
            switch ( factor )
            {
                case EBF_ZERO:
                    r = GL_ZERO;
                    break;
                case EBF_ONE:
                    r = GL_ONE;
                    break;
                case EBF_DST_COLOR:
                    r = GL_DST_COLOR;
                    break;
                case EBF_ONE_MINUS_DST_COLOR:
                    r = GL_ONE_MINUS_DST_COLOR;
                    break;
                case EBF_SRC_COLOR:
                    r = GL_SRC_COLOR;
                    break;
                case EBF_ONE_MINUS_SRC_COLOR:
                    r = GL_ONE_MINUS_SRC_COLOR;
                    break;
                case EBF_SRC_ALPHA:
                    r = GL_SRC_ALPHA;
                    break;
                case EBF_ONE_MINUS_SRC_ALPHA:
                    r = GL_ONE_MINUS_SRC_ALPHA;
                    break;
                case EBF_DST_ALPHA:
                    r = GL_DST_ALPHA;
                    break;
                case EBF_ONE_MINUS_DST_ALPHA:
                    r = GL_ONE_MINUS_DST_ALPHA;
                    break;
                case EBF_SRC_ALPHA_SATURATE:
                    r = GL_SRC_ALPHA_SATURATE;
                    break;
            }
            return r;
        }

        void COGLES2Driver::blendFunc( E_BLEND_FACTOR sFactor, E_BLEND_FACTOR dFactor )
        {
            if ( sFactor != SourceFactor || dFactor != DestFactor )
            {
                SourceFactor = sFactor;
                DestFactor   = dFactor;
                glBlendFunc( getGLBlend( sFactor ), getGLBlend( dFactor ) );
            }
        }


//! Set/unset a clipping plane.
        bool COGLES2Driver::setClipPlane( u32 index, const core::plane3df& plane, bool enable )
        {
            if ( index >= UserClipPlane.size() )
                UserClipPlane.push_back( SUserClipPlane() );

            UserClipPlane[index].Plane = plane;
            UserClipPlane[index].Enabled = enable;
            return true;
        }

//! Enable/disable a clipping plane.
        void COGLES2Driver::enableClipPlane( u32 index, bool enable )
        {
            if ( index >= MaxUserClipPlanes )
                return;

            UserClipPlane[index].Enabled = enable;
        }

//! Get the ClipPlane Count
        u32 COGLES2Driver::getClipPlaneCount() const
        {
            return UserClipPlane.size();
        }

        const core::plane3df& COGLES2Driver::getClipPlane( irr::u32 index ) const
        {
            if ( index < UserClipPlane.size() )
                return UserClipPlane[index].Plane;
            else
                return *(( core::plane3df* )0 );
        }

		core::dimension2du COGLES2Driver::getMaxTextureSize() const
		{
			return core::dimension2du(MaxTextureSize, MaxTextureSize);
		}


    } // end namespace
} // end namespace

#endif // _IRR_COMPILE_WITH_OGLES2_

namespace irr
{
    namespace video
    {

// -----------------------------------
// WINDOWS VERSION
// -----------------------------------
#if defined(_IRR_COMPILE_WITH_X11_DEVICE_) || defined(_IRR_COMPILE_WITH_SDL_DEVICE_) || defined(_IRR_COMPILE_WITH_WINDOWS_DEVICE_) || defined(_IRR_COMPILE_WITH_CONSOLE_DEVICE_)
        IVideoDriver* createOGLES2Driver( const SIrrlichtCreationParameters& params,
                                          video::SExposedVideoData& data, io::IFileSystem* io )
        {
#ifdef _IRR_COMPILE_WITH_OGLES2_
            return new COGLES2Driver( params, data, io );
#else
            return 0;
#endif // _IRR_COMPILE_WITH_OGLES2_
        }
#endif

// -----------------------------------
// MACOSX VERSION
// -----------------------------------
#if defined(_IRR_COMPILE_WITH_OSX_DEVICE_)
        IVideoDriver* createOGLES2Driver( const SIrrlichtCreationParameters& params,
                                          io::IFileSystem* io, CIrrDeviceMacOSX *device )
        {
#ifdef _IRR_COMPILE_WITH_OGLES2_
            return new COGLES2Driver( params, io, device );
#else
            return 0;
#endif //  _IRR_COMPILE_WITH_OGLES2_
        }
#endif // _IRR_COMPILE_WITH_OSX_DEVICE_

// -----------------------------------
// IPHONE VERSION
// -----------------------------------
#if defined(_IRR_COMPILE_WITH_IPHONE_DEVICE_)
        IVideoDriver* createOGLES2Driver( const SIrrlichtCreationParameters& params,
                                          video::SExposedVideoData& data, io::IFileSystem* io,
                                          MIrrIPhoneDevice const & device )
        {
#ifdef _IRR_COMPILE_WITH_OGLES2_
            return new COGLES2Driver( params, data, io, device );
#else
            return 0;
#endif // _IRR_COMPILE_WITH_OGLES2_
        }
#endif // _IRR_COMPILE_WITH_IPHONE_DEVICE_

    } // end namespace
} // end namespace
